Skip to main content

iris_wasm/
crypto.rs

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