rose_wasm/
crypto.rs

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