Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ic_representation_independent_hash instead of a copied hash code #2379

Merged
merged 2 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions demos/vc_issuer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/canister_sig_util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ edition = "2021"
candid.workspace = true
ic-cdk.workspace = true
ic-certification.workspace = true
ic-representation-independent-hash = "2.5"

# other dependencies
hex.workspace = true
Expand Down
117 changes: 8 additions & 109 deletions src/canister_sig_util/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use candid::Principal;
use ic_certification::{Hash, HashTree};
use ic_representation_independent_hash::{representation_independent_hash, Value};
use lazy_static::lazy_static;
use serde::Serialize;
use serde_bytes::ByteBuf;
use sha2::{Digest, Sha256};
use std::collections::HashMap;

pub mod signature_map;

Expand Down Expand Up @@ -141,128 +141,27 @@ pub fn delegation_signature_msg(
expiration: u64,
targets: Option<&Vec<Vec<u8>>>,
) -> Vec<u8> {
use hash::Value;

let mut m = HashMap::new();
m.insert("pubkey", Value::Bytes(pubkey));
m.insert("expiration", Value::U64(expiration));
let mut m: Vec<(String, Value)> = vec![];
m.push(("pubkey".into(), Value::Bytes(pubkey.to_vec())));
m.push(("expiration".into(), Value::Number(expiration)));
if let Some(targets) = targets.as_ref() {
let mut arr = Vec::with_capacity(targets.len());
for t in targets.iter() {
arr.push(Value::Bytes(t.as_ref()));
arr.push(Value::Bytes(t.to_vec()));
}
m.insert("targets", Value::Array(arr));
m.push(("targets".into(), Value::Array(arr)));
}
let map_hash = hash::hash_of_map(m);
let map_hash = representation_independent_hash(m.as_slice());
msg_with_domain(b"ic-request-auth-delegation", &map_hash)
}

pub fn msg_with_domain(sep: &[u8], bytes: &[u8]) -> Vec<u8> {
fn msg_with_domain(sep: &[u8], bytes: &[u8]) -> Vec<u8> {
let mut msg = vec![sep.len() as u8];
msg.append(&mut sep.to_vec());
msg.append(&mut bytes.to_vec());
msg
}

// This is a copy or II's hash.rs, to avoid dependency on II's code.
// TODO: remove the copy and use ic_representation_independent_hash-crate
// once ic_representation_independent_hash::Value supports Array-values.
mod hash {
use ic_certification::Hash;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::convert::AsRef;

#[derive(Clone, Serialize, Deserialize)]
pub enum Value<'a> {
Bytes(#[serde(with = "serde_bytes")] &'a [u8]),
String(&'a str),
U64(u64),
Array(Vec<Value<'a>>),
}

pub fn hash_of_map<S: AsRef<str>>(map: HashMap<S, Value>) -> Hash {
let mut hashes: Vec<Vec<u8>> = Vec::new();
for (key, val) in map.into_iter() {
hashes.push(hash_key_val(key.as_ref(), val));
}

// Computes hash by first sorting by "field name" hash, which is the
// same as sorting by concatenation of H(field name) · H(field value)
// (although in practice it's actually more stable in the presence of
// duplicated field names). Then concatenate all the hashes.
hashes.sort();

let mut hasher = Sha256::new();
for hash in hashes {
hasher.update(&hash);
}

hasher.finalize().into()
}

fn hash_key_val(key: &str, val: Value<'_>) -> Vec<u8> {
let mut key_hash = hash_string(key).to_vec();
let val_hash = hash_val(val);
key_hash.extend_from_slice(&val_hash[..]);
key_hash
}

pub fn hash_string(value: &str) -> Hash {
hash_bytes(value.as_bytes())
}

pub fn hash_bytes(value: impl AsRef<[u8]>) -> Hash {
let mut hasher = Sha256::new();
hasher.update(value.as_ref());
hasher.finalize().into()
}

fn hash_u64(value: u64) -> Hash {
// We need at most ⌈ 64 / 7 ⌉ = 10 bytes to encode a 64 bit
// integer in LEB128.
let mut buf = [0u8; 10];
let mut n = value;
let mut i = 0;

loop {
let byte = (n & 0x7f) as u8;
n >>= 7;

if n == 0 {
buf[i] = byte;
break;
} else {
buf[i] = byte | 0x80;
i += 1;
}
}

hash_bytes(&buf[..=i])
}

// Arrays encoded as the concatenation of the hashes of the encodings of the
// array elements.
fn hash_array(elements: Vec<Value<'_>>) -> Hash {
let mut hasher = Sha256::new();
elements
.into_iter()
// Hash the encoding of all the array elements.
.for_each(|e| hasher.update(&hash_val(e)[..]));
hasher.finalize().into() // hash the concatenation of the hashes.
}

fn hash_val(val: Value<'_>) -> Hash {
match val {
Value::String(string) => hash_string(string),
Value::Bytes(bytes) => hash_bytes(bytes),
Value::U64(integer) => hash_u64(integer),
Value::Array(elements) => hash_array(elements),
}
}
}

#[derive(Serialize)]
struct CanisterSig {
certificate: ByteBuf,
Expand Down
Loading