diff --git a/wallet/Cargo.lock b/wallet/Cargo.lock index 3ae91e6..537390c 100644 --- a/wallet/Cargo.lock +++ b/wallet/Cargo.lock @@ -32,7 +32,7 @@ dependencies = [ "nom", "pin-project", "pinentry", - "rand 0.8.5", + "rand", "rpassword", "rust-embed", "scrypt", @@ -54,7 +54,7 @@ dependencies = [ "hkdf", "io_tee", "nom", - "rand 0.8.5", + "rand", "secrecy", "sha2", ] @@ -121,12 +121,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "autocfg" version = "1.5.0" @@ -186,22 +180,6 @@ dependencies = [ "virtue", ] -[[package]] -name = "bitcoin-io" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" - -[[package]] -name = "bitcoin_hashes" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" -dependencies = [ - "bitcoin-io", - "hex-conservative", -] - [[package]] name = "bitflags" version = "2.9.4" @@ -218,10 +196,16 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.2.35" +name = "bumpalo" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ "find-msvc-tools", "shlex", @@ -357,7 +341,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -441,7 +425,6 @@ dependencies = [ "digest", "elliptic-curve", "rfc6979", - "serdect", "signature", "spki", ] @@ -465,9 +448,8 @@ dependencies = [ "generic-array", "group", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", - "serdect", "subtle", "zeroize", ] @@ -494,7 +476,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -515,9 +497,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" [[package]] name = "fluent" @@ -670,20 +652,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "wasi", + "wasm-bindgen", ] [[package]] @@ -693,7 +665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -715,15 +687,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-conservative" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" -dependencies = [ - "arrayvec", -] - [[package]] name = "hkdf" version = "0.12.4" @@ -874,6 +837,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "k256" version = "0.13.4" @@ -884,7 +857,6 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "serdect", "sha2", "signature", ] @@ -938,6 +910,16 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1127,12 +1109,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "rand" version = "0.8.5" @@ -1140,18 +1116,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_chacha", + "rand_core", ] [[package]] @@ -1161,17 +1127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -1180,16 +1136,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", + "getrandom", ] [[package]] @@ -1300,6 +1247,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -1351,31 +1304,10 @@ dependencies = [ "der", "generic-array", "pkcs8", - "serdect", "subtle", "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" -dependencies = [ - "bitcoin_hashes", - "rand 0.9.2", - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" -dependencies = [ - "cc", -] - [[package]] name = "secrecy" version = "0.10.3" @@ -1438,16 +1370,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serdect" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" -dependencies = [ - "base16ct", - "serde", -] - [[package]] name = "sha2" version = "0.10.9" @@ -1477,10 +1399,10 @@ dependencies = [ "bincode", "clap", "hex", - "secp256k1", "serde", "serde_json", "sha2", + "sha3", "thiserror 2.0.16", ] @@ -1497,7 +1419,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -1694,12 +1616,17 @@ version = "0.1.0" dependencies = [ "age", "bincode", + "getrandom", "hex", + "js-sys", "k256", "serde", "sha3", "shared", "thiserror 2.0.16", + "wasm-bindgen", + "wasm-bindgen-test", + "web-sys", ] [[package]] @@ -1709,12 +1636,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.3+wasi-0.2.4" +name = "wasm-bindgen" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" dependencies = [ - "wit-bindgen", + "cfg-if", + "once_cell", + "rustversion", + "serde", + "serde_json", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80cc7f8a4114fdaa0c58383caf973fc126cf004eba25c9dc639bccd3880d55ad" +dependencies = [ + "js-sys", + "minicov", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ada2ab788d46d4bda04c9d567702a79c8ced14f51f221646a16ed39d0e6a5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "web-sys" +version = "0.3.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +dependencies = [ + "js-sys", + "wasm-bindgen", ] [[package]] @@ -1900,12 +1926,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" -[[package]] -name = "wit-bindgen" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" - [[package]] name = "x25519-dalek" version = "2.0.1" @@ -1913,25 +1933,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core 0.6.4", + "rand_core", "serde", "zeroize", ] [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index c5e9324..754b5d3 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -1,14 +1,30 @@ [package] name = "wallet" version = "0.1.0" -edition = "2024" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] [dependencies] -age = { version = "0.11.1", features = ["cli-common"] } bincode = { version = "2.0.1", features = ["serde"] } hex = "0.4.3" -k256 = { version = "0.13.4", features = ["ecdsa-core", "serde"] } serde = { version = "1.0.219", features = ["derive"] } sha3 = "0.10.8" shared = { path = "../shared" } thiserror = "2.0.16" + +# Native-only dependencies +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +age = { version = "0.11.1", features = ["cli-common"] } +k256 = { version = "0.13.4", features = ["ecdsa", "sha256"] } + +# WASM-only dependencies +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { version = "0.2.101", features = ["serde-serialize"] } +web-sys = { version = "0.3.78", features = ["Window", "Crypto"] } +js-sys = "0.3" +getrandom = { version = "0.2", features = ["js"] } + +[dev-dependencies] +wasm-bindgen-test = "0.3" diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 2fff25c..038eded 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1 +1,11 @@ -pub mod wallet; +pub use wallet::*; + +pub mod wallet { + pub mod wallet; + pub mod storage; + pub mod manager; + + pub use wallet::*; + pub use storage::*; + pub use manager::*; +} diff --git a/wallet/src/wallet/manager.rs b/wallet/src/wallet/manager.rs new file mode 100644 index 0000000..414038a --- /dev/null +++ b/wallet/src/wallet/manager.rs @@ -0,0 +1,52 @@ +use age::secrecy::SecretString; +use shared::core::{SignedTransaction, Transaction}; + +use super::{ + wallet::{ Wallet, WalletError }, + storage::WalletStorage, +}; + +pub struct WalletManager { + storage: S, + wallet: Option +} + +impl WalletManager { + pub fn new(storage: S) -> Self { + Self { + storage, + wallet: None, + } + } + + pub fn create_wallet(&mut self, passphrase: SecretString) -> Result<&Wallet, S::Error> { + let wallet = Wallet::new(); + self.storage.save(&wallet, passphrase)?; + self.wallet = Some(wallet); + Ok(self.wallet.as_ref().unwrap()) + } + + pub fn load_wallet(&mut self, passphrase: SecretString) -> Result<&Wallet, S::Error> { + let wallet = self.storage.load(passphrase)?; + self.wallet = Some(wallet); + Ok(self.wallet.as_ref().unwrap()) + } + + pub fn wallet(&self) -> Option<&Wallet> { + self.wallet.as_ref() + } + + pub fn sign_transaction(&self, transaction: Transaction) -> Result { + match &self.wallet { + Some(wallet) => Ok(wallet.sign(transaction)?), + None => Err(WalletError::NoPrivateKeyProvided), + } + } + + pub fn verify_transaction(&self, transaction: SignedTransaction) -> Result<(), WalletError> { + match &self.wallet { + Some(wallet) => Ok(wallet.verify_signature(&transaction)?), + None => Err(WalletError::NoPrivateKeyProvided), + } + } +} diff --git a/wallet/src/wallet/storage.rs b/wallet/src/wallet/storage.rs new file mode 100644 index 0000000..5714b5a --- /dev/null +++ b/wallet/src/wallet/storage.rs @@ -0,0 +1,102 @@ +use std::path::PathBuf; + +use age::secrecy::SecretString; + +use super::wallet::{ WalletError, Wallet }; + +static BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); + +pub trait WalletStorage { + type Error; + + fn load(&self, passphrase: SecretString) -> Result; + fn save(&self, wallet: &Wallet, passphrase: SecretString) -> Result<(), Self::Error>; +} + +pub struct FileStorage { + path: PathBuf, +} + +impl FileStorage { + pub fn new(path: PathBuf) -> Self { + Self { + path + } + } +} + +impl WalletStorage for FileStorage { + type Error = WalletError; + + fn load(&self, passphrase: SecretString) -> Result { + use std::io::Read; + + let file = std::fs::OpenOptions::new() + .read(true) + .open(&self.path)?; + let decryptor = age::Decryptor::new(file)?; + let mut buf = Vec::new(); + let identity = age::scrypt::Identity::new(passphrase); + let mut reader = decryptor.decrypt(std::iter::once(&identity as &dyn age::Identity))?; + reader.read_to_end(&mut buf)?; + + let wallet: Wallet = bincode::decode_from_slice(&buf, BINCODE_CONFIG)?.0; + Ok(wallet) + } + + fn save(&self, wallet: &Wallet, passphrase: SecretString) -> Result<(), Self::Error> { + use std::io::Write; + + let bin_wallet = bincode::encode_to_vec::<&Wallet, _>(wallet, BINCODE_CONFIG)?; + + let file = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&self.path)?; + + let encryptor = age::Encryptor::with_user_passphrase(passphrase); + let mut writer = encryptor.wrap_output(file)?; + writer.write_all(&bin_wallet)?; + writer.finish()?; + Ok(()) + } +} + +pub struct MemoryStorage { + data: std::sync::Mutex>>, +} + +impl MemoryStorage { + pub fn new() -> Self { + Self { + data: std::sync::Mutex::new(None), + } + } +} + +impl WalletStorage for MemoryStorage { + type Error = WalletError; + + fn save(&self, wallet: &Wallet, _passphrase: SecretString) -> Result<(), Self::Error> { + let bin_wallet = bincode::encode_to_vec(wallet, BINCODE_CONFIG)?; + *self.data.lock().unwrap() = Some(bin_wallet); + Ok(()) + } + + fn load(&self, _passphrase: SecretString) -> Result { + let data = self.data.lock().unwrap(); + match data.as_ref() { + Some(wallet) => { + let wallet: Wallet = bincode::decode_from_slice(wallet, BINCODE_CONFIG)?.0; + Ok(wallet) + } + None => { + Err(WalletError::IO(std::io::Error::new( + std::io::ErrorKind::NotFound, + "No wallet data in memory" + ))) + } + } + } +} diff --git a/wallet/src/wallet.rs b/wallet/src/wallet/wallet.rs similarity index 52% rename from wallet/src/wallet.rs rename to wallet/src/wallet/wallet.rs index 30898d5..154c9ef 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet/wallet.rs @@ -1,4 +1,3 @@ -use age::Identity; use bincode::{Decode, Encode}; use k256::sha2::Digest; use k256::ecdsa::{ @@ -13,11 +12,6 @@ use shared::core::{ Transaction, SignedTransaction, Address, }; use k256::elliptic_curve::rand_core::OsRng; use sha3::Keccak256; -use std::io::Read; -use std::path; - -static BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); - #[derive(Debug, thiserror::Error)] pub enum WalletError { #[error("No Private Key present in Wallet")] @@ -61,20 +55,73 @@ pub struct Wallet { impl Wallet { - fn verify_self_signature(&self, sign_tx: &SignedTransaction) -> Result<(), WalletError>{ + #[cfg(not(target_arch = "wasm32"))] + pub fn new() -> Self { + let pk = Wallet::generate_private_key(); + let address = Self::public_key_to_address(&pk.verifying_key()); + Self { + nonce: 0, + balance: 0, + address, + private_key: pk.to_bytes().into() + } + } + + #[cfg(target_arch = "wasm32")] + pub fn new() -> Result { + let pk = Wallet::generate_private_key()?; + let address = Self::public_key_to_address(&pk.verifying_key()); + Self { + nonce: 0, + balance: 0, + address, + private_key: pk.to_bytes().into() + } + } + + pub fn from_private_key(private_key: [u8; 32]) -> Result { + let pk = SigningKey::from_bytes(&private_key.into())?; + let address = Self::public_key_to_address(&pk.verifying_key()); + Ok(Self { + nonce: 0, + balance: 0, + address, + private_key + }) + } + + pub fn verify_signature(&self, sign_tx: &SignedTransaction) -> Result<(), WalletError>{ if let Some(rec_id) = RecoveryId::from_byte(sign_tx.recovery_id()) { let sig = Signature::from_slice(sign_tx.signature())?; - println!("Signature recovered"); let hash = sign_tx.tx().hash(); - let pub_key = VerifyingKey::recover_from_prehash(&hash, &sig, rec_id).unwrap(); - // let pub_key = self.public_key()?; - println!("pubkey recovered"); + let pub_key = VerifyingKey::recover_from_msg(&hash, &sig, rec_id).unwrap(); + Ok(pub_key.verify(&hash, &sig).unwrap()) } else { Err(WalletError::InvalidRecoveryId(sign_tx.recovery_id())) } } + #[cfg(target_arch = "wasm32")] + pub fn generate_private_key() -> Result { + let mut bytes = [0u8; 32]; + + let crypto = web_sys::window() + .ok_or_else(|| WalletError::WasmError("No window object".to_string()))? + .crypto() + .map_err(|_| WalletError::WasmError("No crypto object".to_string()))?; + + let array = Uint8Array::new_with_length(32); + crypto.get_random_values_with_u8_array(&mut array.view_mut()[..]) + .map_err(|_| WalletError::WasmError("Failed to get random values".to_string()))?; + + array.copy_to(&mut bytes); + + SigningKey::from_bytes(&bytes.into()) + .map_err(|e| WalletError::SignatureError(e)) + } + + #[cfg(not(target_arch = "wasm32"))] pub fn generate_private_key() -> SigningKey { SigningKey::random(&mut OsRng) } @@ -88,7 +135,7 @@ impl Wallet { } pub fn public_key(&self) -> Result { - let pk = SigningKey::from_bytes(&self.private_key.into())?; + let pk = self.private_key()?; Ok(*pk.verifying_key()) } @@ -107,41 +154,11 @@ impl Wallet { address } - fn load(path: path::PathBuf) -> Result { - let file = std::fs::OpenOptions::new().read(true).open(path)?; - let decryptor = age::Decryptor::new(file)?; - let passphrase = match age::cli_common::read_secret( - "Unlock your Wallet", - "Please enter the passphrase", - Some("confirm?") - ) { - Ok(p) => p, - Err(e) => return Err(WalletError::PassphraseError(format!("{e}"))) - }; - let mut buf = Vec::new(); - let identity = age::scrypt::Identity::new(passphrase); - let mut reader = decryptor.decrypt(std::iter::once(&identity as &dyn Identity))?; - reader.read_to_end(&mut buf)?; - - let wallet: Wallet = bincode::decode_from_slice(&buf, BINCODE_CONFIG)?.0; - Ok(wallet) - } - - fn new() -> Self { - let pk = Wallet::generate_private_key(); - let address = Self::public_key_to_address(&pk.verifying_key()); - Self { - nonce: 0, - balance: 0, - address, - private_key: pk.to_bytes().into() - } - } pub fn sign(&self, transaction: Transaction) -> Result { let hash = transaction.hash(); let pk = SigningKey::from_bytes(&self.private_key.into())?; - let (signature, recovery_id) = pk.sign_prehash_recoverable(&hash)?; + let (signature, recovery_id) = pk.sign_recoverable(&hash)?; Ok(SignedTransaction::new( transaction, signature.to_bytes().into(), @@ -150,6 +167,46 @@ impl Wallet { } } +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl Wallet { + #[wasm_bindgen(constructor)] + pub fn new_js() -> Result { + Wallet::new().map_err(|e| JsValue::from_str(&e.to_string())) + } + + #[wasm_bindgen] + pub fn from_private_key_js(private_key: &[u8]) -> Result { + if private_key.len() != 32 { + return Err(JsValue::from_str("Private key must be 32 bytes")); + } + let mut key_array = [0u8; 32]; + key_array.copy_from_slice(private_key); + Wallet::from_private_key(key_array) + .map_err(|e| JsValue::from_str(&e.to_string())) + } + + #[wasm_bindgen(getter)] + pub fn address_js(&self) -> Vec { + self.address().to_vec() + } + + #[wasm_bindgen(getter)] + pub fn nonce_js(&self) -> u64 { + self.nonce() + } + + #[wasm_bindgen] + pub fn address_hex(&self) -> String { + format!("0x{}", hex::encode(self.address())) + } + + #[wasm_bindgen] + pub fn private_key_hex(&self) -> String { + hex::encode(self.private_key) + } +} + #[test] fn acc_new_sign_no_key() { let wallet = Wallet::new(); @@ -166,65 +223,3 @@ fn acc_new_sign_no_key() { let ret = wallet.sign(tx); assert!(matches!(ret, Err(WalletError::NoPrivateKeyProvided))) } - -#[cfg(test)] -mod test { - const WALLET_PATH: &str = "./wallet.age"; - - use std::io::Write; - - use age::secrecy::SecretString; - - use super::*; - - mod new { - use super::*; - #[test] - fn wallet_new() -> Result<(), WalletError> { - let wallet = Wallet::new(); - let passphrase = SecretString::from("password"); - - let bin_wallet = bincode::encode_to_vec::(wallet, BINCODE_CONFIG)?; - - let file = std::fs::OpenOptions::new().create(true).write(true).truncate(true).open(WALLET_PATH)?; - let encryptor = age::Encryptor::with_user_passphrase(passphrase); - let mut writer = encryptor.wrap_output(file)?; - writer.write_all(&bin_wallet)?; - writer.finish()?; - Ok(()) - } - - } - mod load { - use super::*; - - #[test] - fn wallet_load() -> Result<(), WalletError> { - let wallet = Wallet::load(WALLET_PATH.into())?; - Ok(()) - } - } - - mod sign { - use super::*; - - #[test] - fn load_sign_verify() -> Result<(), WalletError> { - let wallet = Wallet::load(WALLET_PATH.into())?; - - let tx = Transaction::new(wallet.address(), Default::default(), 500, wallet.nonce() + 1, "This is my data".to_string()); - // let mut hash = Keccak256::new(); - // hash.update(tx.data()); - let hash = tx.hash(); - let pk: ecdsa::SigningKey = wallet.private_key()?; - let (signature, recovery_id) = pk.sign_recoverable(&hash)?; - // let sig_tx = SignedTransaction::new(tx, signature.to_bytes().into(), recovery_id.into()); - let vk = pk.verifying_key(); - vk.verify(&hash, &signature).unwrap(); - - // wallet.verify_self_signature(&sig_tx)?; - Ok(()) - } - } -} -