wasm_crypto_box/
lib.rs

1//! wasm-crypto-box — Browser-safe WebAssembly wrapper for ed25519
2//!
3//! This crate exposes a tiny set of helpers for generating ed25519 keypairs
4//! and performing signing/verification from JavaScript via `wasm-bindgen`.
5//!
6//! Example (JS):
7//!
8//! ```js
9//! import init, { generate_keypair, sign, verify } from './pkg/wasm_crypto_box.js';
10//! await init();
11//! const { secretKey, publicKey } = generate_keypair();
12//! const msg = new TextEncoder().encode('hello');
13//! const sig = sign(msg, secretKey);
14//! console.log('verified:', verify(msg, sig, publicKey));
15//! ```
16
17use wasm_bindgen::prelude::*;
18use js_sys::Uint8Array;
19use getrandom::getrandom;
20use ed25519_dalek::{PublicKey, SecretKey, Signature, Verifier, ExpandedSecretKey};
21
22#[wasm_bindgen]
23pub fn generate_keypair() -> Result<js_sys::Object, JsValue> {
24    // 32 bytes seed for an ed25519 secret key
25    let mut seed = [0u8; 32];
26    getrandom(&mut seed).map_err(|e| JsValue::from_str(&format!("rng error: {}", e)))?;
27
28    let secret = SecretKey::from_bytes(&seed).map_err(|e| JsValue::from_str(&e.to_string()))?;
29    let public = PublicKey::from(&secret);
30
31    let sk_arr = Uint8Array::from(&secret.to_bytes()[..]);
32    let pk_arr = Uint8Array::from(&public.to_bytes()[..]);
33
34    let obj = js_sys::Object::new();
35    js_sys::Reflect::set(&obj, &JsValue::from_str("secretKey"), &sk_arr.into())?;
36    js_sys::Reflect::set(&obj, &JsValue::from_str("publicKey"), &pk_arr.into())?;
37
38    Ok(obj)
39}
40
41#[wasm_bindgen]
42pub fn sign(message: &[u8], secret_key: &[u8]) -> Result<Uint8Array, JsValue> {
43    if secret_key.len() != 32 {
44        return Err(JsValue::from_str("secret_key must be 32 bytes"));
45    }
46
47    let secret = SecretKey::from_bytes(secret_key).map_err(|e| JsValue::from_str(&e.to_string()))?;
48    let public = PublicKey::from(&secret);
49    let expanded = ExpandedSecretKey::from(&secret);
50
51    let sig: Signature = expanded.sign(message, &public);
52    Ok(Uint8Array::from(&sig.to_bytes()[..]))
53}
54
55#[wasm_bindgen]
56pub fn verify(message: &[u8], signature: &[u8], public_key: &[u8]) -> Result<bool, JsValue> {
57    if signature.len() != 64 {
58        return Err(JsValue::from_str("signature must be 64 bytes"));
59    }
60    if public_key.len() != 32 {
61        return Err(JsValue::from_str("public_key must be 32 bytes"));
62    }
63
64    let sig = Signature::from_bytes(signature).map_err(|e| JsValue::from_str(&e.to_string()))?;
65    let public = PublicKey::from_bytes(public_key).map_err(|e| JsValue::from_str(&e.to_string()))?;
66
67    Ok(public.verify(message, &sig).is_ok())
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn sign_and_verify_roundtrip() {
76        let mut seed = [0u8; 32];
77        getrandom(&mut seed).expect("rng");
78        let secret = SecretKey::from_bytes(&seed).unwrap();
79        let public = PublicKey::from(&secret);
80        let message = b"hello wasm";
81
82        let expanded = ExpandedSecretKey::from(&secret);
83        let sig = expanded.sign(message, &public);
84
85        assert!(public.verify(message, &sig).is_ok());
86    }
87}