1use alloc::{format, string::String, vec::Vec};
38use ark_bn254::Fr;
39use ark_ff::PrimeField;
40use ark_serialize::CanonicalSerialize;
41use crypto_secretbox::{KeyInit, Nonce, XSalsa20Poly1305, aead::Aead};
42use sha2::{Digest, Sha256};
43use wasm_bindgen::prelude::*;
44use x25519_dalek::{PublicKey, StaticSecret};
45
46#[wasm_bindgen]
66pub fn derive_keypair_from_signature(signature: &[u8]) -> Result<Vec<u8>, JsValue> {
67 derive_keypair_from_signature_internal(signature).map_err(|e| JsValue::from_str(&e))
68}
69
70fn derive_keypair_from_signature_internal(signature: &[u8]) -> Result<Vec<u8>, String> {
71 if signature.len() != 64 {
72 return Err("Signature must be 64 bytes (Ed25519)".into());
73 }
74
75 let mut hasher = Sha256::new();
77 hasher.update(signature);
78 let seed = hasher.finalize();
79
80 let mut secret_bytes = [0u8; 32];
82 secret_bytes.copy_from_slice(&seed);
83
84 let secret = StaticSecret::from(secret_bytes);
85 let public = PublicKey::from(&secret);
86
87 let mut result = Vec::with_capacity(64);
89 result.extend_from_slice(public.as_bytes());
90 result.extend_from_slice(&secret.to_bytes());
91
92 Ok(result)
93}
94
95#[wasm_bindgen]
113pub fn derive_note_private_key(signature: &[u8]) -> Result<Vec<u8>, JsValue> {
114 if signature.len() != 64 {
115 return Err(JsValue::from_str("Signature must be 64 bytes (Ed25519)"));
116 }
117
118 let mut hasher = Sha256::new();
121 hasher.update(signature);
122 let key = hasher.finalize();
123
124 let field = Fr::from_le_bytes_mod_order(&key);
126
127 let mut result: Vec<u8> = Vec::with_capacity(32);
129 field
130 .serialize_compressed(&mut result)
131 .expect("Serialization failed");
132
133 Ok(result)
134}
135
136#[wasm_bindgen]
149pub fn generate_random_blinding() -> Result<Vec<u8>, JsValue> {
150 let mut random_bytes = [0u8; 32];
151 getrandom::getrandom(&mut random_bytes)
152 .map_err(|e| JsValue::from_str(&format!("Random generation failed: {}", e)))?;
153
154 let scalar = Fr::from_le_bytes_mod_order(&random_bytes);
156
157 let mut result = Vec::with_capacity(32);
159 scalar
160 .serialize_compressed(&mut result)
161 .map_err(|e| JsValue::from_str(&format!("Serialization failed: {}", e)))?;
162 Ok(result)
163}
164
165#[wasm_bindgen]
185pub fn encrypt_note_data(
186 recipient_pubkey_bytes: &[u8],
187 plaintext: &[u8],
188) -> Result<Vec<u8>, JsValue> {
189 encrypt_note_data_internal(recipient_pubkey_bytes, plaintext).map_err(|e| JsValue::from_str(&e))
190}
191
192fn encrypt_note_data_internal(
193 recipient_pubkey_bytes: &[u8],
194 plaintext: &[u8],
195) -> Result<Vec<u8>, String> {
196 if recipient_pubkey_bytes.len() != 32 {
197 return Err("Recipient public key must be 32 bytes".into());
198 }
199 if plaintext.len() != 40 {
200 return Err("Plaintext must be 40 bytes (8 amount + 32 blinding)".into());
201 }
202
203 let mut ephemeral_bytes = [0u8; 32];
205 getrandom::getrandom(&mut ephemeral_bytes)
206 .map_err(|e| format!("Failed to generate ephemeral key: {}", e))?;
207
208 let ephemeral_secret = StaticSecret::from(ephemeral_bytes);
209 let ephemeral_public = PublicKey::from(&ephemeral_secret);
210
211 let recipient_public = PublicKey::from(
213 *<&[u8; 32]>::try_from(recipient_pubkey_bytes)
214 .map_err(|_| "Invalid recipient public key")?,
215 );
216 let shared_secret = ephemeral_secret.diffie_hellman(&recipient_public);
217
218 let cipher = XSalsa20Poly1305::new(shared_secret.as_bytes().into());
220
221 let mut nonce_bytes = [0u8; 24];
223 getrandom::getrandom(&mut nonce_bytes)
224 .map_err(|e| format!("Failed to generate nonce: {}", e))?;
225 let nonce = Nonce::from(nonce_bytes);
226
227 let ciphertext = cipher
229 .encrypt(&nonce, plaintext)
230 .map_err(|e| format!("Encryption failed: {:?}", e))?;
231
232 let capacity = ciphertext
235 .len()
236 .checked_add(56)
237 .expect("Integer overflow on encryption output size");
238 let mut result = Vec::with_capacity(capacity);
239 result.extend_from_slice(ephemeral_public.as_bytes());
240 result.extend_from_slice(&nonce_bytes);
241 result.extend_from_slice(&ciphertext);
242
243 Ok(result)
244}
245
246#[wasm_bindgen]
259pub fn decrypt_note_data(
260 private_key_bytes: &[u8],
261 encrypted_data: &[u8],
262) -> Result<Vec<u8>, JsValue> {
263 decrypt_note_data_internal(private_key_bytes, encrypted_data).map_err(|e| JsValue::from_str(&e))
264}
265
266fn decrypt_note_data_internal(
267 private_key_bytes: &[u8],
268 encrypted_data: &[u8],
269) -> Result<Vec<u8>, String> {
270 if private_key_bytes.len() != 32 {
271 return Err("Private key must be 32 bytes".into());
272 }
273
274 if encrypted_data.len() < 112 {
277 return Err("Encrypted data too short".into());
278 }
279
280 let ephemeral_pubkey = &encrypted_data[0..32];
282 let nonce_bytes = &encrypted_data[32..56];
283 let ciphertext_with_tag = &encrypted_data[56..];
284
285 let our_secret = StaticSecret::from(
287 *<&[u8; 32]>::try_from(private_key_bytes).map_err(|_| "Invalid private key")?,
288 );
289
290 let ephemeral_public = PublicKey::from(
292 *<&[u8; 32]>::try_from(ephemeral_pubkey).map_err(|_| "Invalid ephemeral public key")?,
293 );
294 let shared_secret = our_secret.diffie_hellman(&ephemeral_public);
295
296 let cipher = XSalsa20Poly1305::new(shared_secret.as_bytes().into());
298
299 let mut nonce_array = [0u8; 24];
301 nonce_array.copy_from_slice(nonce_bytes);
302 let nonce = Nonce::from(nonce_array);
303
304 match cipher.decrypt(&nonce, ciphertext_with_tag) {
306 Ok(plaintext) => Ok(plaintext),
307 Err(_) => {
308 Ok(Vec::new()) }
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn test_derive_keypair_determinism() {
320 let signature = [1u8; 64];
321 let keys1 = derive_keypair_from_signature_internal(&signature).expect("Derivation failed");
322 let keys2 = derive_keypair_from_signature_internal(&signature).expect("Derivation failed");
323 assert_eq!(keys1, keys2);
324 assert_eq!(keys1.len(), 64);
325 }
326
327 #[test]
328 fn test_encryption_roundtrip() {
329 let recipient_sig = [2u8; 64];
330 let recip_keys =
331 derive_keypair_from_signature_internal(&recipient_sig).expect("Derivation failed");
332 let pub_key = &recip_keys[0..32];
333 let priv_key = &recip_keys[32..64];
334
335 let amount = [10u8; 8];
337 let blinding = [20u8; 32];
338 let mut plaintext = Vec::with_capacity(40);
339 plaintext.extend_from_slice(&amount);
340 plaintext.extend_from_slice(&blinding);
341
342 let encrypted = encrypt_note_data_internal(pub_key, &plaintext).expect("Encryption failed");
343 assert!(encrypted.len() >= 112);
344
345 let decrypted =
346 decrypt_note_data_internal(priv_key, &encrypted).expect("Decryption failed");
347 assert_eq!(decrypted, plaintext);
348 }
349
350 #[test]
351 fn test_decrypt_failure_wrong_key() {
352 let alice_sig = [3u8; 64];
353 let bob_sig = [4u8; 64];
354
355 let alice_keys =
356 derive_keypair_from_signature_internal(&alice_sig).expect("Derivation failed");
357 let bob_keys = derive_keypair_from_signature_internal(&bob_sig).expect("Derivation failed");
358
359 let alice_pub = &alice_keys[0..32];
361 let plaintext = [0u8; 40];
362 let encrypted =
363 encrypt_note_data_internal(alice_pub, &plaintext).expect("Encryption failed");
364
365 let bob_priv = &bob_keys[32..64];
367 let decrypted = decrypt_note_data_internal(bob_priv, &encrypted)
368 .expect("Decryption should handle failure gracefully");
369
370 assert!(decrypted.is_empty());
372 }
373
374 #[test]
375 fn test_invalid_input_lengths() {
376 let sig = [5u8; 64];
377 let keys = derive_keypair_from_signature_internal(&sig)
378 .expect("Derivation failed in test_invalid_input_lengths");
379 let pub_key = &keys[0..32];
380
381 let res = encrypt_note_data_internal(pub_key, &[0u8; 39]);
383 assert!(res.is_err());
384
385 let res = encrypt_note_data_internal(&[0u8; 31], &[0u8; 40]);
387 assert!(res.is_err());
388 }
389}