//! WASM-compatible cryptography for Synor web wallet. //! //! This crate provides cryptographic primitives that can be compiled to WebAssembly //! for use in the Synor web wallet. It includes: //! //! - Ed25519 signature generation and verification //! - Key derivation from BIP-39 mnemonics //! - Bech32m address encoding //! - Dilithium3 (ML-DSA-65) post-quantum signatures //! //! # Usage in JavaScript //! //! ```javascript //! import init, { Keypair, Mnemonic, DilithiumSigningKey } from 'synor-crypto-wasm'; //! //! await init(); //! //! // Generate mnemonic //! const mnemonic = Mnemonic.generate(24); //! console.log(mnemonic.phrase()); //! //! // Create Ed25519 keypair from mnemonic //! const keypair = Keypair.fromMnemonic(mnemonic.phrase(), ""); //! console.log(keypair.address("mainnet")); //! //! // Sign and verify with Ed25519 //! const message = new TextEncoder().encode("Hello Synor!"); //! const signature = keypair.sign(message); //! const isValid = keypair.verify(message, signature); //! //! // Post-quantum signatures with Dilithium3 //! const pqKey = new DilithiumSigningKey(); //! const pqSignature = pqKey.sign(message); //! const pqValid = pqKey.verify(message, pqSignature); //! ``` use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey}; use rand::rngs::OsRng; use sha3::{Digest, Sha3_256}; use wasm_bindgen::prelude::*; use zeroize::Zeroize; mod address; mod dilithium_wasm; mod mnemonic_wasm; pub use address::*; pub use dilithium_wasm::*; pub use mnemonic_wasm::*; /// Initialize the WASM module. #[wasm_bindgen(start)] pub fn init() { // WASM initialization - can be extended for panic hooks, logging, etc. } /// Ed25519 keypair for signing transactions. #[wasm_bindgen] pub struct Keypair { signing_key: SigningKey, #[wasm_bindgen(skip)] pub seed: [u8; 32], } #[wasm_bindgen] impl Keypair { /// Generate a new random keypair. #[wasm_bindgen(constructor)] pub fn new() -> Result { let signing_key = SigningKey::generate(&mut OsRng); let seed = signing_key.to_bytes(); Ok(Keypair { signing_key, seed }) } /// Create a keypair from a 32-byte seed. #[wasm_bindgen(js_name = fromSeed)] pub fn from_seed(seed: &[u8]) -> Result { if seed.len() != 32 { return Err(JsValue::from_str("Seed must be exactly 32 bytes")); } let mut seed_arr = [0u8; 32]; seed_arr.copy_from_slice(seed); let signing_key = SigningKey::from_bytes(&seed_arr); Ok(Keypair { signing_key, seed: seed_arr, }) } /// Create a keypair from a BIP-39 mnemonic phrase. #[wasm_bindgen(js_name = fromMnemonic)] pub fn from_mnemonic(phrase: &str, passphrase: &str) -> Result { let mnemonic = bip39::Mnemonic::from_phrase(phrase, bip39::Language::English) .map_err(|e| JsValue::from_str(&format!("Invalid mnemonic: {:?}", e)))?; // Derive seed from mnemonic let seed = bip39::Seed::new(&mnemonic, passphrase); let seed_bytes = seed.as_bytes(); // Use first 32 bytes for Ed25519 let mut ed_seed = [0u8; 32]; ed_seed.copy_from_slice(&seed_bytes[..32]); let signing_key = SigningKey::from_bytes(&ed_seed); Ok(Keypair { signing_key, seed: ed_seed, }) } /// Get the public key as hex string. #[wasm_bindgen(js_name = publicKeyHex)] pub fn public_key_hex(&self) -> String { hex::encode(self.signing_key.verifying_key().to_bytes()) } /// Get the public key as bytes. #[wasm_bindgen(js_name = publicKeyBytes)] pub fn public_key_bytes(&self) -> Vec { self.signing_key.verifying_key().to_bytes().to_vec() } /// Get the Synor address for this keypair. #[wasm_bindgen] pub fn address(&self, network: &str) -> Result { let pubkey = self.signing_key.verifying_key().to_bytes(); address::encode_address(network, &pubkey) } /// Sign a message. #[wasm_bindgen] pub fn sign(&self, message: &[u8]) -> Vec { let signature = self.signing_key.sign(message); signature.to_bytes().to_vec() } /// Verify a signature. #[wasm_bindgen] pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result { if signature.len() != 64 { return Err(JsValue::from_str("Signature must be 64 bytes")); } let sig_bytes: [u8; 64] = signature .try_into() .map_err(|_| JsValue::from_str("Invalid signature length"))?; let sig = Signature::from_bytes(&sig_bytes); use ed25519_dalek::Verifier; Ok(self .signing_key .verifying_key() .verify(message, &sig) .is_ok()) } } impl Default for Keypair { fn default() -> Self { Self::new().expect("Failed to generate keypair") } } impl Drop for Keypair { fn drop(&mut self) { self.seed.zeroize(); } } /// Verify a signature with a public key. #[wasm_bindgen(js_name = verifyWithPublicKey)] pub fn verify_with_public_key( public_key: &[u8], message: &[u8], signature: &[u8], ) -> Result { if public_key.len() != 32 { return Err(JsValue::from_str("Public key must be 32 bytes")); } if signature.len() != 64 { return Err(JsValue::from_str("Signature must be 64 bytes")); } let pk_bytes: [u8; 32] = public_key .try_into() .map_err(|_| JsValue::from_str("Invalid public key"))?; let sig_bytes: [u8; 64] = signature .try_into() .map_err(|_| JsValue::from_str("Invalid signature"))?; let verifying_key = VerifyingKey::from_bytes(&pk_bytes) .map_err(|_| JsValue::from_str("Invalid public key format"))?; let signature = Signature::from_bytes(&sig_bytes); use ed25519_dalek::Verifier; Ok(verifying_key.verify(message, &signature).is_ok()) } /// Compute SHA3-256 hash. #[wasm_bindgen(js_name = sha3_256)] pub fn sha3_256_hash(data: &[u8]) -> Vec { let mut hasher = Sha3_256::new(); hasher.update(data); hasher.finalize().to_vec() } /// Compute BLAKE3 hash. #[wasm_bindgen(js_name = blake3)] pub fn blake3_hash(data: &[u8]) -> Vec { blake3::hash(data).as_bytes().to_vec() } /// Derive key using HKDF-SHA256. #[wasm_bindgen(js_name = deriveKey)] pub fn derive_key( input_key: &[u8], salt: &[u8], info: &[u8], output_len: usize, ) -> Result, JsValue> { use hkdf::Hkdf; use sha3::Sha3_256; let hk = Hkdf::::new(Some(salt), input_key); let mut output = vec![0u8; output_len]; hk.expand(info, &mut output) .map_err(|_| JsValue::from_str("HKDF expansion failed"))?; Ok(output) } // bip39 crate is used in mnemonic_wasm.rs #[cfg(test)] mod tests { use super::*; #[test] fn test_keypair_generation() { let keypair = Keypair::new().unwrap(); assert_eq!(keypair.public_key_bytes().len(), 32); } #[test] fn test_sign_verify() { let keypair = Keypair::new().unwrap(); let message = b"Hello, Synor!"; let signature = keypair.sign(message); assert!(keypair.verify(message, &signature).unwrap()); } #[test] fn test_address_generation() { let keypair = Keypair::new().unwrap(); let address = keypair.address("mainnet").unwrap(); assert!(address.starts_with("synor1")); } #[test] fn test_mnemonic_keypair() { let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; let keypair = Keypair::from_mnemonic(phrase, "").unwrap(); assert_eq!(keypair.public_key_bytes().len(), 32); } }