# Detailed Implementation Plan ## 1. Create Directory Structure ``` src/ ├── lib.rs # Main entry point, exports WASM functions ├── api/ # Public API modules │ ├── mod.rs # Re-exports public API functions │ ├── keypair.rs # Public keypair API │ └── symmetric.rs # Public symmetric encryption API ├── core/ # Internal implementation modules │ ├── mod.rs # Re-exports core functionality │ ├── error.rs # Error types and conversions │ ├── keypair.rs # Core keypair implementation │ └── symmetric.rs # Core symmetric encryption implementation └── tests/ # Test modules ├── keypair_tests.rs # Tests for keypair functionality └── symmetric_tests.rs # Tests for symmetric encryption ``` ## 2. Implementation Steps ### Step 1: Create Core Error Module (src/core/error.rs) ```rust //! Error types for cryptographic operations. /// Errors that can occur during cryptographic operations. #[derive(Debug)] pub enum CryptoError { /// The keypair has not been initialized. KeypairNotInitialized, /// The keypair has already been initialized. KeypairAlreadyInitialized, /// Signature verification failed. SignatureVerificationFailed, /// The signature format is invalid. SignatureFormatError, /// Encryption operation failed. EncryptionFailed, /// Decryption operation failed. DecryptionFailed, /// The key length is invalid. InvalidKeyLength, /// Other error with description. Other(String), } impl std::fmt::Display for CryptoError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CryptoError::KeypairNotInitialized => write!(f, "Keypair not initialized"), CryptoError::KeypairAlreadyInitialized => write!(f, "Keypair already initialized"), CryptoError::SignatureVerificationFailed => write!(f, "Signature verification failed"), CryptoError::SignatureFormatError => write!(f, "Invalid signature format"), CryptoError::EncryptionFailed => write!(f, "Encryption failed"), CryptoError::DecryptionFailed => write!(f, "Decryption failed"), CryptoError::InvalidKeyLength => write!(f, "Invalid key length"), CryptoError::Other(s) => write!(f, "Crypto error: {}", s), } } } impl std::error::Error for CryptoError {} /// Converts a CryptoError to an i32 status code for WebAssembly. pub fn error_to_status_code(err: CryptoError) -> i32 { match err { CryptoError::KeypairNotInitialized => -1, CryptoError::KeypairAlreadyInitialized => -2, CryptoError::SignatureVerificationFailed => -3, CryptoError::SignatureFormatError => -4, CryptoError::EncryptionFailed => -5, CryptoError::DecryptionFailed => -6, CryptoError::InvalidKeyLength => -7, CryptoError::Other(_) => -99, } } ``` ### Step 2: Create Core Keypair Module (src/core/keypair.rs) ```rust //! Core implementation of keypair functionality. use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature}; use once_cell::sync::OnceCell; use rand::rngs::OsRng; use super::error::CryptoError; /// A keypair for signing and verifying messages. #[derive(Debug)] pub struct KeyPair { pub verifying_key: VerifyingKey, pub signing_key: SigningKey, } /// Global keypair instance. static KEYPAIR: OnceCell = OnceCell::new(); /// Initializes the global keypair. /// /// # Returns /// /// * `Ok(())` if the keypair was initialized successfully. /// * `Err(CryptoError::KeypairAlreadyInitialized)` if the keypair was already initialized. pub fn keypair_new() -> Result<(), CryptoError> { let signing_key = SigningKey::random(&mut OsRng); let verifying_key = VerifyingKey::from(&signing_key); let keypair = KeyPair { verifying_key, signing_key }; KEYPAIR.set(keypair).map_err(|_| CryptoError::KeypairAlreadyInitialized) } /// Gets the public key bytes. /// /// # Returns /// /// * `Ok(Vec)` containing the public key bytes. /// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. pub fn keypair_pub_key() -> Result, CryptoError> { KEYPAIR.get() .ok_or(CryptoError::KeypairNotInitialized) .map(|kp| kp.verifying_key.to_sec1_bytes().to_vec()) } /// Signs a message. /// /// # Arguments /// /// * `message` - The message to sign. /// /// # Returns /// /// * `Ok(Vec)` containing the signature bytes. /// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. pub fn keypair_sign(message: &[u8]) -> Result, CryptoError> { KEYPAIR.get() .ok_or(CryptoError::KeypairNotInitialized) .map(|kp| { let signature: Signature = kp.signing_key.sign(message); signature.to_bytes().to_vec() }) } /// Verifies a message signature. /// /// # Arguments /// /// * `message` - The message that was signed. /// * `signature_bytes` - The signature to verify. /// /// # Returns /// /// * `Ok(true)` if the signature is valid. /// * `Ok(false)` if the signature is invalid. /// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. /// * `Err(CryptoError::SignatureFormatError)` if the signature format is invalid. pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result { let keypair = KEYPAIR.get().ok_or(CryptoError::KeypairNotInitialized)?; let signature = Signature::from_bytes(signature_bytes.into()) .map_err(|_| CryptoError::SignatureFormatError)?; match keypair.verifying_key.verify(message, &signature) { Ok(_) => Ok(true), Err(_) => Ok(false), // Verification failed, but operation was successful } } ``` ### Step 3: Create Core Symmetric Module (src/core/symmetric.rs) ```rust //! Core implementation of symmetric encryption functionality. use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce}; use chacha20poly1305::aead::Aead; use rand::{rngs::OsRng, RngCore}; use super::error::CryptoError; /// The size of the nonce in bytes. const NONCE_SIZE: usize = 12; /// Generates a random 32-byte symmetric key. /// /// # Returns /// /// A 32-byte array containing the random key. pub fn generate_symmetric_key() -> [u8; 32] { let mut key = [0u8; 32]; OsRng.fill_bytes(&mut key); key } /// Encrypts data using ChaCha20Poly1305 with an internally generated nonce. /// /// The nonce is appended to the ciphertext so it can be extracted during decryption. /// /// # Arguments /// /// * `key` - The encryption key (should be 32 bytes). /// * `message` - The message to encrypt. /// /// # Returns /// /// * `Ok(Vec)` containing the ciphertext with the nonce appended. /// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. /// * `Err(CryptoError::EncryptionFailed)` if encryption fails. pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, CryptoError> { // Create cipher let cipher = ChaCha20Poly1305::new_from_slice(key) .map_err(|_| CryptoError::InvalidKeyLength)?; // Generate random nonce let mut nonce_bytes = [0u8; NONCE_SIZE]; OsRng.fill_bytes(&mut nonce_bytes); let nonce = Nonce::from_slice(&nonce_bytes); // Encrypt message let ciphertext = cipher.encrypt(nonce, message) .map_err(|_| CryptoError::EncryptionFailed)?; // Append nonce to ciphertext let mut result = ciphertext; result.extend_from_slice(&nonce_bytes); Ok(result) } /// Decrypts data using ChaCha20Poly1305, extracting the nonce from the ciphertext. /// /// # Arguments /// /// * `key` - The decryption key (should be 32 bytes). /// * `ciphertext_with_nonce` - The ciphertext with the nonce appended. /// /// # Returns /// /// * `Ok(Vec)` containing the decrypted message. /// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. /// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short. pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result, CryptoError> { // Check if ciphertext is long enough to contain a nonce if ciphertext_with_nonce.len() <= NONCE_SIZE { return Err(CryptoError::DecryptionFailed); } // Extract nonce from the end of ciphertext let ciphertext_len = ciphertext_with_nonce.len() - NONCE_SIZE; let ciphertext = &ciphertext_with_nonce[0..ciphertext_len]; let nonce_bytes = &ciphertext_with_nonce[ciphertext_len..]; // Create cipher let cipher = ChaCha20Poly1305::new_from_slice(key) .map_err(|_| CryptoError::InvalidKeyLength)?; let nonce = Nonce::from_slice(nonce_bytes); // Decrypt message cipher.decrypt(nonce, ciphertext) .map_err(|_| CryptoError::DecryptionFailed) } ``` ### Step 4: Create Core Module (src/core/mod.rs) ```rust //! Core cryptographic functionality. pub mod error; pub mod keypair; pub mod symmetric; // Re-export commonly used items pub use error::CryptoError; ``` ### Step 5: Create API Keypair Module (src/api/keypair.rs) ```rust //! Public API for keypair operations. use crate::core::keypair; use crate::core::error::CryptoError; /// Initializes a new keypair for signing and verification. /// /// # Returns /// /// * `Ok(())` if the keypair was initialized successfully. /// * `Err(CryptoError::KeypairAlreadyInitialized)` if a keypair was already initialized. pub fn new() -> Result<(), CryptoError> { keypair::keypair_new() } /// Gets the public key of the initialized keypair. /// /// # Returns /// /// * `Ok(Vec)` containing the public key bytes. /// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized. pub fn pub_key() -> Result, CryptoError> { keypair::keypair_pub_key() } /// Signs a message using the initialized keypair. /// /// # Arguments /// /// * `message` - The message to sign. /// /// # Returns /// /// * `Ok(Vec)` containing the signature bytes. /// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized. pub fn sign(message: &[u8]) -> Result, CryptoError> { keypair::keypair_sign(message) } /// Verifies a signature against a message. /// /// # Arguments /// /// * `message` - The message that was signed. /// * `signature` - The signature to verify. /// /// # Returns /// /// * `Ok(true)` if the signature is valid. /// * `Ok(false)` if the signature is invalid. /// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized. /// * `Err(CryptoError::SignatureFormatError)` if the signature format is invalid. pub fn verify(message: &[u8], signature: &[u8]) -> Result { keypair::keypair_verify(message, signature) } ``` ### Step 6: Create API Symmetric Module (src/api/symmetric.rs) ```rust //! Public API for symmetric encryption operations. use crate::core::symmetric; use crate::core::error::CryptoError; /// Generates a random 32-byte symmetric key. /// /// # Returns /// /// A 32-byte array containing the random key. pub fn generate_key() -> [u8; 32] { symmetric::generate_symmetric_key() } /// Encrypts data using ChaCha20Poly1305. /// /// A random nonce is generated internally and appended to the ciphertext. /// /// # Arguments /// /// * `key` - The encryption key (should be 32 bytes). /// * `message` - The message to encrypt. /// /// # Returns /// /// * `Ok(Vec)` containing the ciphertext with the nonce appended. /// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. /// * `Err(CryptoError::EncryptionFailed)` if encryption fails. pub fn encrypt(key: &[u8], message: &[u8]) -> Result, CryptoError> { symmetric::encrypt_symmetric(key, message) } /// Decrypts data using ChaCha20Poly1305. /// /// The nonce is extracted from the end of the ciphertext. /// /// # Arguments /// /// * `key` - The decryption key (should be 32 bytes). /// * `ciphertext` - The ciphertext with the nonce appended. /// /// # Returns /// /// * `Ok(Vec)` containing the decrypted message. /// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. /// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short. pub fn decrypt(key: &[u8], ciphertext: &[u8]) -> Result, CryptoError> { symmetric::decrypt_symmetric(key, ciphertext) } ``` ### Step 7: Create API Module (src/api/mod.rs) ```rust //! Public API for cryptographic operations. pub mod keypair; pub mod symmetric; // Re-export commonly used items pub use crate::core::error::CryptoError; ``` ### Step 8: Update lib.rs ```rust //! WebAssembly module for cryptographic operations. use wasm_bindgen::prelude::*; use web_sys::console; // Import modules mod api; mod core; // Re-export for internal use use api::keypair; use api::symmetric; use core::error::CryptoError; use core::error::error_to_status_code; // This is like the `main` function, except for JavaScript. #[wasm_bindgen(start)] pub fn main_js() -> Result<(), JsValue> { // This provides better error messages in debug mode. // It's disabled in release mode so it doesn't bloat up the file size. #[cfg(debug_assertions)] console_error_panic_hook::set_once(); console::log_1(&JsValue::from_str("Crypto module initialized")); Ok(()) } // --- WebAssembly Exports --- #[wasm_bindgen] pub fn keypair_new() -> i32 { match keypair::new() { Ok(_) => 0, // Success Err(e) => error_to_status_code(e), } } #[wasm_bindgen] pub fn keypair_pub_key() -> Result, JsValue> { keypair::pub_key() .map_err(|e| JsValue::from_str(&e.to_string())) } #[wasm_bindgen] pub fn keypair_sign(message: &[u8]) -> Result, JsValue> { keypair::sign(message) .map_err(|e| JsValue::from_str(&e.to_string())) } #[wasm_bindgen] pub fn keypair_verify(message: &[u8], signature: &[u8]) -> Result { keypair::verify(message, signature) .map_err(|e| JsValue::from_str(&e.to_string())) } #[wasm_bindgen] pub fn generate_symmetric_key() -> Vec { symmetric::generate_key().to_vec() } #[wasm_bindgen] pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, JsValue> { symmetric::encrypt(key, message) .map_err(|e| JsValue::from_str(&e.to_string())) } #[wasm_bindgen] pub fn decrypt_symmetric(key: &[u8], ciphertext: &[u8]) -> Result, JsValue> { symmetric::decrypt(key, ciphertext) .map_err(|e| JsValue::from_str(&e.to_string())) } ``` ### Step 9: Create Test Files #### src/tests/keypair_tests.rs ```rust //! Tests for keypair functionality. #[cfg(test)] mod tests { use crate::core::keypair; // Helper to ensure keypair is initialized for tests that need it. fn ensure_keypair_initialized() { // Use try_init which doesn't panic if already initialized let _ = keypair::keypair_new(); assert!(keypair::KEYPAIR.get().is_some(), "KEYPAIR should be initialized"); } #[test] fn test_keypair_generation_and_retrieval() { let _ = keypair::keypair_new(); // Ignore error if already initialized by another test let pub_key = keypair::keypair_pub_key().expect("Should be able to get pub key after init"); assert!(!pub_key.is_empty(), "Public key should not be empty"); // Basic check for SEC1 format (0x02, 0x03, or 0x04 prefix) assert!(pub_key.len() == 33 || pub_key.len() == 65, "Public key length is incorrect"); assert!(pub_key[0] == 0x02 || pub_key[0] == 0x03 || pub_key[0] == 0x04, "Invalid SEC1 format start byte"); } #[test] fn test_sign_verify_valid() { ensure_keypair_initialized(); let message = b"this is a test message"; let signature = keypair::keypair_sign(message).expect("Signing failed"); assert!(!signature.is_empty(), "Signature should not be empty"); let is_valid = keypair::keypair_verify(message, &signature).expect("Verification failed"); assert!(is_valid, "Signature should be valid"); } #[test] fn test_verify_invalid_signature() { ensure_keypair_initialized(); let message = b"another test message"; let mut invalid_signature = keypair::keypair_sign(message).expect("Signing failed"); // Tamper with the signature invalid_signature[0] = invalid_signature[0].wrapping_add(1); let is_valid = keypair::keypair_verify(message, &invalid_signature).expect("Verification process failed"); assert!(!is_valid, "Tampered signature should be invalid"); } #[test] fn test_verify_wrong_message() { ensure_keypair_initialized(); let message = b"original message"; let wrong_message = b"different message"; let signature = keypair::keypair_sign(message).expect("Signing failed"); let is_valid = keypair::keypair_verify(wrong_message, &signature).expect("Verification process failed"); assert!(!is_valid, "Signature should be invalid for a different message"); } } ``` #### src/tests/symmetric_tests.rs ```rust //! Tests for symmetric encryption functionality. #[cfg(test)] mod tests { use crate::core::symmetric; use crate::core::error::CryptoError; #[test] fn test_generate_symmetric_key() { let key = symmetric::generate_symmetric_key(); assert_eq!(key.len(), 32, "Symmetric key length should be 32 bytes"); // Check if it's not all zeros (highly unlikely for random) assert!(key.iter().any(|&byte| byte != 0), "Key should not be all zeros"); } #[test] fn test_encrypt_decrypt_symmetric() { let key = symmetric::generate_symmetric_key(); let message = b"super secret data"; let ciphertext = symmetric::encrypt_symmetric(&key, message) .expect("Encryption failed"); assert_ne!(message, ciphertext.as_slice(), "Ciphertext should be different from message"); let decrypted_message = symmetric::decrypt_symmetric(&key, &ciphertext) .expect("Decryption failed"); assert_eq!(message, decrypted_message.as_slice(), "Decrypted message should match original"); } #[test] fn test_decrypt_wrong_key() { let key1 = symmetric::generate_symmetric_key(); let key2 = symmetric::generate_symmetric_key(); // Different key let message = b"data for key1"; let ciphertext = symmetric::encrypt_symmetric(&key1, message) .expect("Encryption failed"); let result = symmetric::decrypt_symmetric(&key2, &ciphertext); assert!(result.is_err(), "Decryption should fail with the wrong key"); assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed"); } #[test] fn test_decrypt_tampered_ciphertext() { let key = symmetric::generate_symmetric_key(); let message = b"important data"; let mut ciphertext = symmetric::encrypt_symmetric(&key, message) .expect("Encryption failed"); // Tamper with ciphertext (e.g., flip a bit) if !ciphertext.is_empty() { ciphertext[0] ^= 0x01; } else { panic!("Ciphertext is empty, cannot tamper"); } let result = symmetric::decrypt_symmetric(&key, &ciphertext); assert!(result.is_err(), "Decryption should fail with tampered ciphertext"); assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed"); } } ``` ### Step 10: Update README.md ```markdown # Rust WebAssembly Cryptography Module This project provides a WebAssembly module written in Rust that offers cryptographic functionality for web applications. ## Features - **Asymmetric Cryptography** - ECDSA keypair generation - Message signing - Signature verification - **Symmetric Cryptography** - ChaCha20Poly1305 encryption/decryption - Secure key generation ## Prerequisites Before you begin, ensure you have the following installed: - [Rust](https://www.rust-lang.org/tools/install) (1.70.0 or later) - [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) (0.10.0 or later) - A modern web browser that supports WebAssembly ## Project Structure ``` webassembly/ ├── src/ │ ├── api/ # Public API modules │ │ ├── keypair.rs # Public keypair API │ │ ├── mod.rs # API module exports │ │ └── symmetric.rs # Public symmetric encryption API │ ├── core/ # Internal implementation modules │ │ ├── error.rs # Error types and conversions │ │ ├── keypair.rs # Core keypair implementation │ │ ├── mod.rs # Core module exports │ │ └── symmetric.rs # Core symmetric encryption implementation │ ├── tests/ # Test modules │ │ ├── keypair_tests.rs # Tests for keypair functionality │ │ └── symmetric_tests.rs # Tests for symmetric encryption │ └── lib.rs # Main entry point, exports WASM functions ├── www/ │ ├── index.html # Example HTML page │ └── js/ │ └── index.js # JavaScript code to load and use the WebAssembly module ├── Cargo.toml # Rust package configuration └── README.md # This file ``` ## Building the WebAssembly Module To build the WebAssembly module, run: ```bash wasm-pack build --target web ``` This will create a `pkg` directory containing the compiled WebAssembly module and JavaScript bindings. ## Running the Example After building the WebAssembly module, you can run the example using a local web server: ```bash cd www npm install npm start ``` Then open your browser and navigate to http://localhost:8080. ## API Reference ### Keypair Operations ```javascript // Initialize a new keypair const result = await wasm.keypair_new(); if (result === 0) { console.log("Keypair initialized successfully"); } // Get the public key const pubKey = await wasm.keypair_pub_key(); // Sign a message const message = new TextEncoder().encode("Hello, world!"); const signature = await wasm.keypair_sign(message); // Verify a signature const isValid = await wasm.keypair_verify(message, signature); console.log("Signature valid:", isValid); ``` ### Symmetric Encryption ```javascript // Generate a symmetric key const key = wasm.generate_symmetric_key(); // Encrypt a message const message = new TextEncoder().encode("Secret message"); const ciphertext = await wasm.encrypt_symmetric(key, message); // Decrypt a message const decrypted = await wasm.decrypt_symmetric(key, ciphertext); const decryptedText = new TextDecoder().decode(decrypted); console.log("Decrypted:", decryptedText); ``` ## Security Considerations - The keypair is stored in memory and is not persisted between page reloads. - The symmetric encryption uses ChaCha20Poly1305, which provides authenticated encryption. - The nonce for symmetric encryption is generated randomly and appended to the ciphertext. ## License This project is licensed under the MIT License - see the LICENSE file for details.