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