From 6dcf11c7189a00535f9e9fdb9e60bc06ed8d2f38 Mon Sep 17 00:00:00 2001 From: victor Date: Sat, 6 Sep 2025 00:04:29 +0200 Subject: [PATCH] bless --- testing/keys/crypt.age | 8 +- testing/keys/src/main.rs | 7 +- wallet/Cargo.lock | 2 + wallet/Cargo.toml | 4 +- wallet/src/wallet.rs | 191 ++++++++++++++++++++++++++------------- wallet/wallet.age | Bin 0 -> 236 bytes 6 files changed, 143 insertions(+), 69 deletions(-) create mode 100644 wallet/wallet.age diff --git a/testing/keys/crypt.age b/testing/keys/crypt.age index ff71184..fd67a0d 100644 --- a/testing/keys/crypt.age +++ b/testing/keys/crypt.age @@ -1,5 +1,5 @@ age-encryption.org/v1 --> scrypt FP9Jzf9WMGgQj2HZPAbuDw 14 -i+HlcYKckQXUKAtoY8SIjJUz15IE2GucgQM0sZxLx78 ---- 5JVy3rsJpvCXTl41B6/k/aC0HqoAdyfH4I6efffwr/w --|QǑ-D3<%|~韥)"+(}Kvɐ'#StUuX,| Z>C :Y#:ԕLNM˂ *- HnDUOcoJ6^cX0ؼpK\~uX|; |7 DMD;_l`K^+A \ No newline at end of file +-> scrypt qz/wL52nh3MG/kb96jc2Mg 13 +auC858eJBPN/QknA7lj7CdnrmKEm8EOSasOQpBkkJ/c +--- K1MwOdhmY33yGi7USChiazGeOj9uUGKCYvVOltWSA7A +,-Ҧsf$Loz A3vbɄ r4ܑ!HDbIB*5s \ No newline at end of file diff --git a/testing/keys/src/main.rs b/testing/keys/src/main.rs index 8efe1d5..7a6b354 100644 --- a/testing/keys/src/main.rs +++ b/testing/keys/src/main.rs @@ -15,6 +15,7 @@ mod tests { use std::io::{ Read, Write }; + #[test] fn encrypt() { let passphrase = age::secrecy::SecretString::from("password"); @@ -25,10 +26,10 @@ mod tests { private_key: "thisisprivate".to_string() }; - let b = bincode::serde::encode_to_vec::(wallet, bincode::config::Configuration::default()).unwrap(); + let b = bincode::serde::encode_to_vec::(wallet, bincode::config::standard()).unwrap(); let encryptor = age::Encryptor::with_user_passphrase(passphrase); - let file = std::fs::OpenOptions::new().create(true).write(true).open(OUT_FILE).unwrap(); + let file = std::fs::OpenOptions::new().create(true).truncate(true).write(true).open(OUT_FILE).unwrap(); let mut stream = encryptor.wrap_output(file).unwrap(); stream.write_all(&b).unwrap(); stream.finish().unwrap(); @@ -47,7 +48,7 @@ mod tests { decryptor.decrypt(std::iter::once(&identity as &dyn age::Identity)).unwrap().read_to_end(&mut buf).unwrap(); - let (wallet, _): (Wallet, usize) = bincode::serde::decode_from_slice::(&buf, bincode::config::Configuration::default()).unwrap(); + let (wallet, _): (Wallet, usize) = bincode::serde::decode_from_slice::(&buf, bincode::config::standard()).unwrap(); dbg!(&wallet); } diff --git a/wallet/Cargo.lock b/wallet/Cargo.lock index ce7a452..3ae91e6 100644 --- a/wallet/Cargo.lock +++ b/wallet/Cargo.lock @@ -1693,8 +1693,10 @@ name = "wallet" version = "0.1.0" dependencies = [ "age", + "bincode", "hex", "k256", + "serde", "sha3", "shared", "thiserror 2.0.16", diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 317201e..c5e9324 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -5,8 +5,10 @@ edition = "2024" [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 = ["serde"] } +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" diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index d786c6c..30898d5 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -1,3 +1,5 @@ +use age::Identity; +use bincode::{Decode, Encode}; use k256::sha2::Digest; use k256::ecdsa::{ self, @@ -5,13 +7,17 @@ use k256::ecdsa::{ VerifyingKey, RecoveryId, Signature, + signature::Verifier, }; 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")] @@ -20,6 +26,12 @@ pub enum WalletError { #[error("Signature error: {0}")] SignatureError(#[from] ecdsa::Error), + #[error("Encryption Error: {0}")] + EncryptionError(#[from] age::EncryptError), + + #[error("Decryption Error: {0}")] + DecryptionError(#[from] age::DecryptError), + #[error("Provided Recovery ID is invalid: {0}")] InvalidRecoveryId(u8), @@ -27,26 +39,39 @@ pub enum WalletError { IO(#[from] std::io::Error), #[error("")] - InvalidHashLength + InvalidHashLength, + + #[error("Passphrase Error: {0}")] + PassphraseError(String), + + #[error("Decode Error: {0}")] + DecodeError(#[from] bincode::error::DecodeError), + + #[error("Encode Error: {0}")] + EncodeError(#[from] bincode::error::EncodeError), } -#[derive(Debug)] +#[derive(Debug, Encode, Decode)] pub struct Wallet { address: Address, balance: u64, nonce: u64, - private_key: Option, + private_key: [u8; 32], } impl Wallet { - fn verify_signature(tx: &SignedTransaction) -> Result{ - if let Some(rec_id) = RecoveryId::from_byte(tx.recovery_id()) { - let sig = Signature::from_slice(tx.signature())?; - let hash = tx.tx().hash(); - Ok(VerifyingKey::recover_from_prehash(&hash, &sig, rec_id)?) + fn verify_self_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"); + Ok(pub_key.verify(&hash, &sig).unwrap()) } else { - Err(WalletError::InvalidRecoveryId(tx.recovery_id())) + Err(WalletError::InvalidRecoveryId(sign_tx.recovery_id())) } } @@ -54,8 +79,8 @@ impl Wallet { SigningKey::random(&mut OsRng) } - pub fn address(&self) -> &[u8] { - &self.address + pub fn address(&self) -> [u8; 20] { + self.address.clone() } pub fn nonce(&self) -> u64 { @@ -63,15 +88,15 @@ impl Wallet { } pub fn public_key(&self) -> Result { - if let Some(pk) = &self.private_key { - Ok(*pk.verifying_key()) - } else { - Err(WalletError::NoPrivateKeyProvided) - } + let pk = SigningKey::from_bytes(&self.private_key.into())?; + Ok(*pk.verifying_key()) } - pub fn verifying_key_to_address(private_key: &SigningKey) -> Address { - let public_key = private_key.verifying_key(); + pub fn private_key(&self) -> Result { + Ok(SigningKey::from_bytes(&self.private_key.into())?) + } + + pub fn public_key_to_address(public_key: &VerifyingKey) -> Address { let public_key_bytes = public_key.to_encoded_point(false); let public_key_bytes = public_key_bytes.as_bytes(); @@ -83,44 +108,54 @@ impl Wallet { } fn load(path: path::PathBuf) -> Result { - let content = std::fs::read(path)?; + 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(pk: Option) -> Self { - let address = if let Some(pk) = &pk { - Self::verifying_key_to_address(pk) - } else { - Address::default() - }; + 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 + private_key: pk.to_bytes().into() } } pub fn sign(&self, transaction: Transaction) -> Result { let hash = transaction.hash(); - if let Some(pk) = &self.private_key { - let (signature, recovery_id) = pk.sign_prehash_recoverable(&hash)?; - Ok(SignedTransaction::new( - transaction, - signature.to_bytes().into(), - recovery_id.into() - )) - } else { - Err(WalletError::NoPrivateKeyProvided) - } + let pk = SigningKey::from_bytes(&self.private_key.into())?; + let (signature, recovery_id) = pk.sign_prehash_recoverable(&hash)?; + Ok(SignedTransaction::new( + transaction, + signature.to_bytes().into(), + recovery_id.into() + )) } } #[test] fn acc_new_sign_no_key() { - let wallet = Wallet::new(None); + let wallet = Wallet::new(); let to_address: [u8; 20] = [1u8; 20]; let mut wallet_addr = [0u8; 20]; - wallet_addr.copy_from_slice(wallet.address()); + wallet_addr.copy_from_slice(&wallet.address()); let tx = Transaction::new( wallet_addr, to_address, @@ -132,30 +167,64 @@ fn acc_new_sign_no_key() { assert!(matches!(ret, Err(WalletError::NoPrivateKeyProvided))) } -#[test] -fn acc_new_sign_with_key() -> Result<(), WalletError> { - let pk = Wallet::generate_private_key(); - let wallet = Wallet::new(Some(pk)); - let orig_public_key = wallet.public_key()?; - let mut wallet_addr = [0u8; 20]; - let to_address = [1u8; 20]; - wallet_addr.copy_from_slice(wallet.address()); - let tx = Transaction::new( - wallet_addr, - to_address, - 500, - wallet.nonce(), - format!("") - ); - let ret = wallet.sign(tx); - assert!(matches!(ret, Ok(SignedTransaction{..}))); - let msg = ret.unwrap(); - let pub_key = Wallet::verify_signature(&msg)?; +#[cfg(test)] +mod test { + const WALLET_PATH: &str = "./wallet.age"; - let orig_pub_key_bytes = orig_public_key.to_encoded_point(false); - let new_pub_key_bytes = pub_key.to_encoded_point(false); + use std::io::Write; - assert_eq!(orig_pub_key_bytes, new_pub_key_bytes); + use age::secrecy::SecretString; - Ok(()) + 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(()) + } + } } + diff --git a/wallet/wallet.age b/wallet/wallet.age new file mode 100644 index 0000000000000000000000000000000000000000..972019e3ecc81e3d2b26d7710ac9c7f0e84f0ffc GIT binary patch literal 236 zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR1428$`Ul_h&S7Uu-`q!?5N=Y^YQ=7oin z1S%MsaQSFERc2HsR%ZJ8Ck40!<{GAJhezaj1eY1-=QueVXSkRq<%F6ix`vg7a_Q>o zDug?h7?me$dxhnvl?C`E8JK4Jy8HPArI&>HxfYfL1{C