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 #[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#[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#[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#[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#[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#[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#[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#[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}