ic_sis 0.2.1

Integrate Sui wallet-based authentication (SIS) with applications on the Internet Computer (ICP) platform. Supports BCS serialization, intent signing, and multiple signature schemes.
Documentation
use ic_certified_map::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(crate) fn hash_of_map<S: AsRef<str>>(map: HashMap<S, Value>) -> Hash {
    let mut hashes = map
        .into_iter()
        .map(|(key, val)| hash_key_value(key.as_ref(), val))
        .collect::<Vec<_>>();

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

    hasher.finalize().into()
}

pub(crate) fn hash_with_domain(sep: &[u8], bytes: &[u8]) -> Hash {
    let mut hasher = Sha256::new();
    hasher.update([sep.len() as u8]);
    hasher.update(sep);
    hasher.update(bytes);
    hasher.finalize().into()
}

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

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

pub(crate) 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 {
    let mut buf = [0u8; 10];
    let mut n = value;
    let mut i = 0;

    loop {
        let byte = (n & 0x7f) as u8;
        n >>= 7;
        buf[i] = byte | if n != 0 { 0x80 } else { 0 };

        if n == 0 {
            break;
        }
        i += 1;
    }

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

fn hash_array(elements: Vec<Value<'_>>) -> Hash {
    let mut hasher = Sha256::new();
    for element in elements {
        hasher.update(&hash_value(element)[..]);
    }

    hasher.finalize().into()
}

fn hash_value(val: Value<'_>) -> Hash {
    match val {
        Value::String(s) => hash_string(s),
        Value::Bytes(b) => hash_bytes(b),
        Value::U64(n) => hash_u64(n),
        Value::Array(a) => hash_array(a),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use hex_literal::hex;

    #[test]
    fn message_id_string_reference_1() {
        assert_eq!(
            hash_string("request_type"),
            hex!("769e6f87bdda39c859642b74ce9763cdd37cb1cd672733e8c54efaa33ab78af9"),
        );
    }

    #[test]
    fn message_id_string_reference_2() {
        assert_eq!(
            hash_string("call"),
            hex!("7edb360f06acaef2cc80dba16cf563f199d347db4443da04da0c8173e3f9e4ed"),
        );
    }

    #[test]
    fn message_id_bytes_reference() {
        assert_eq!(
            hash_bytes(&[68, 73, 68, 76, 0, 253, 42][..]),
            hex!("6c0b2ae49718f6995c02ac5700c9c789d7b7862a0d53e6d40a73f1fcd2f70189")
        );
    }
}