iris_wasm/
crypto.rs

1use ibig::UBig;
2use serde::{Deserialize, Serialize};
3use wasm_bindgen::prelude::*;
4
5use iris_crypto::cheetah::{PrivateKey, PublicKey, Signature};
6use iris_crypto::slip10::{derive_master_key as derive_master_key_internal, ExtendedKey};
7
8#[wasm_bindgen(js_name = Signature)]
9#[derive(Clone, Serialize, Deserialize)]
10pub struct WasmSignature {
11    #[wasm_bindgen(skip)]
12    pub c: Vec<u8>,
13    #[wasm_bindgen(skip)]
14    pub s: Vec<u8>,
15}
16
17#[wasm_bindgen(js_class = Signature)]
18impl WasmSignature {
19    #[wasm_bindgen(getter)]
20    pub fn c(&self) -> Vec<u8> {
21        self.c.clone()
22    }
23
24    #[wasm_bindgen(getter)]
25    pub fn s(&self) -> Vec<u8> {
26        self.s.clone()
27    }
28
29    fn from_internal(sig: &Signature) -> Self {
30        Self {
31            c: sig.c.to_be_bytes(),
32            s: sig.s.to_be_bytes(),
33        }
34    }
35}
36
37#[wasm_bindgen(js_name = ExtendedKey)]
38#[derive(Serialize, Deserialize)]
39pub struct WasmExtendedKey {
40    #[wasm_bindgen(skip)]
41    pub private_key: Option<Vec<u8>>,
42    #[wasm_bindgen(skip)]
43    pub public_key: Vec<u8>,
44    #[wasm_bindgen(skip)]
45    pub chain_code: Vec<u8>,
46}
47
48#[wasm_bindgen(js_class = ExtendedKey)]
49impl WasmExtendedKey {
50    #[wasm_bindgen(getter, js_name = privateKey)]
51    pub fn private_key(&self) -> Option<Vec<u8>> {
52        self.private_key.clone()
53    }
54
55    #[wasm_bindgen(getter, js_name = publicKey)]
56    pub fn public_key(&self) -> Vec<u8> {
57        self.public_key.clone()
58    }
59
60    #[wasm_bindgen(getter, js_name = chainCode)]
61    pub fn chain_code(&self) -> Vec<u8> {
62        self.chain_code.clone()
63    }
64
65    /// Derive a child key at the given index
66    #[wasm_bindgen(js_name = deriveChild)]
67    pub fn derive_child(&self, index: u32) -> Result<WasmExtendedKey, JsValue> {
68        let extended_key = self.to_internal().map_err(|e| JsValue::from_str(&e))?;
69
70        let child = extended_key.derive_child(index);
71        Ok(WasmExtendedKey::from_internal(&child))
72    }
73
74    fn to_internal(&self) -> Result<ExtendedKey, String> {
75        let private_key = if let Some(pk_bytes) = &self.private_key {
76            if pk_bytes.len() != 32 {
77                return Err("Private key must be 32 bytes".to_string());
78            }
79            Some(PrivateKey(UBig::from_be_bytes(pk_bytes)))
80        } else {
81            None
82        };
83
84        if self.public_key.len() != 97 {
85            return Err("Public key must be 97 bytes".to_string());
86        }
87        let mut pub_bytes = [0u8; 97];
88        pub_bytes.copy_from_slice(&self.public_key);
89        let public_key = PublicKey::from_be_bytes(&pub_bytes);
90
91        if self.chain_code.len() != 32 {
92            return Err("Chain code must be 32 bytes".to_string());
93        }
94        let mut chain_code = [0u8; 32];
95        chain_code.copy_from_slice(&self.chain_code);
96
97        Ok(ExtendedKey {
98            private_key,
99            public_key,
100            chain_code,
101        })
102    }
103
104    fn from_internal(key: &ExtendedKey) -> Self {
105        WasmExtendedKey {
106            private_key: key.private_key.as_ref().map(|pk| pk.to_be_bytes().to_vec()),
107            public_key: key.public_key.to_be_bytes().to_vec(),
108            chain_code: key.chain_code.to_vec(),
109        }
110    }
111}
112
113/// Derive master key from seed bytes
114#[wasm_bindgen(js_name = deriveMasterKey)]
115pub fn derive_master_key(seed: &[u8]) -> WasmExtendedKey {
116    let key = derive_master_key_internal(seed);
117    WasmExtendedKey::from_internal(&key)
118}
119
120/// Derive master key from BIP39 mnemonic phrase
121#[wasm_bindgen(js_name = deriveMasterKeyFromMnemonic)]
122pub fn derive_master_key_from_mnemonic(
123    mnemonic: &str,
124    passphrase: Option<String>,
125) -> Result<WasmExtendedKey, JsValue> {
126    use bip39::Mnemonic;
127
128    let mnemonic = Mnemonic::parse(mnemonic)
129        .map_err(|e| JsValue::from_str(&format!("Invalid mnemonic: {}", e)))?;
130
131    let seed = mnemonic.to_seed(passphrase.as_deref().unwrap_or(""));
132    Ok(derive_master_key(&seed))
133}
134
135/// Hash a public key to get its digest (for use in PKH)
136#[wasm_bindgen(js_name = hashPublicKey)]
137pub fn hash_public_key(public_key_bytes: &[u8]) -> Result<String, JsValue> {
138    use iris_ztd::Hashable;
139
140    if public_key_bytes.len() != 97 {
141        return Err(JsValue::from_str("Public key must be 97 bytes"));
142    }
143
144    let mut pub_bytes = [0u8; 97];
145    pub_bytes.copy_from_slice(public_key_bytes);
146    let public_key = PublicKey::from_be_bytes(&pub_bytes);
147
148    let digest = public_key.hash();
149    Ok(digest.to_string())
150}
151
152/// Hash a u64 value
153#[wasm_bindgen(js_name = hashU64)]
154pub fn hash_u64(value: u64) -> String {
155    use iris_ztd::Hashable;
156    let digest = value.hash();
157    digest.to_string()
158}
159
160/// Hash a noun (jam as input)
161#[wasm_bindgen(js_name = hashNoun)]
162pub fn hash_noun(noun: &[u8]) -> Result<String, JsValue> {
163    use iris_ztd::{cue, Hashable};
164    let noun = cue(noun).ok_or("Unable to cue noun")?;
165    let digest = noun.hash();
166    Ok(digest.to_string())
167}
168
169/// Sign a message string with a private key
170#[wasm_bindgen(js_name = signMessage)]
171pub fn sign_message(private_key_bytes: &[u8], message: &str) -> Result<WasmSignature, JsValue> {
172    use iris_ztd::{Belt, Hashable, NounEncode};
173    if private_key_bytes.len() != 32 {
174        return Err(JsValue::from_str("Private key must be 32 bytes"));
175    }
176    let private_key = PrivateKey(UBig::from_be_bytes(private_key_bytes));
177    let digest = Belt::from_bytes(message.as_bytes()).to_noun().hash();
178    Ok(WasmSignature::from_internal(&private_key.sign(&digest)))
179}