This commit is contained in:
victor 2025-09-06 00:04:29 +02:00
parent 891f93de6b
commit 6dcf11c718
6 changed files with 143 additions and 69 deletions

View File

@ -1,5 +1,5 @@
age-encryption.org/v1 age-encryption.org/v1
-> scrypt FP9Jzf9WMGgQj2HZPAbuDw 14 -> scrypt qz/wL52nh3MG/kb96jc2Mg 13
i+HlcYKckQXUKAtoY8SIjJUz15IE2GucgQM0sZxLx78 auC858eJBPN/QknA7lj7CdnrmKEm8EOSasOQpBkkJ/c
--- 5JVy3rsJpvCXTl41B6/k/aC0HqoAdyfH4I6efffwr/w --- K1MwOdhmY33yGi7USChiazGeOj9uUGKCYvVOltWSA7A
-éŰÂ|Q÷ÚúÇ‘-D‰3´<%’Ą|~<7E>¨<07><>韥ť)ęŔÂ"ŠČ+ůńá(Ýţ°}<7D>ÂéëKüv”É<>'#<23><>°SťtUuîX,’ů|‡ ďŚÝZ>CËőĐ šó•Ú:Y”#úÔ:Áť×Ô•Ţă LNMĽô“ËěŐ Ö*-´¸ čôăHnD<6E>˛ŁŃU«OcÚoůJ×ţŁ^ĺłc¸ňŽý«¦X<>0ŘĽp¸K\’š—ę~âuX|Ě; |ßÎ7 D¸MÄD‡;_źlő`K†^Ž+ěAË ,-¨íÒ¦sf$LàoõÁzžìÇ A3Á³ºvbÉ„¡ r4ܶùÀ!ÕHæDäâb÷±¤IBêó*<2A>5<EFBFBD>s<EFBFBD>ÚÓ

View File

@ -15,6 +15,7 @@ mod tests {
use std::io::{ Read, Write }; use std::io::{ Read, Write };
#[test]
fn encrypt() { fn encrypt() {
let passphrase = age::secrecy::SecretString::from("password"); let passphrase = age::secrecy::SecretString::from("password");
@ -25,10 +26,10 @@ mod tests {
private_key: "thisisprivate".to_string() private_key: "thisisprivate".to_string()
}; };
let b = bincode::serde::encode_to_vec::<Wallet, bincode::config::Configuration>(wallet, bincode::config::Configuration::default()).unwrap(); let b = bincode::serde::encode_to_vec::<Wallet, bincode::config::Configuration>(wallet, bincode::config::standard()).unwrap();
let encryptor = age::Encryptor::with_user_passphrase(passphrase); 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(); let mut stream = encryptor.wrap_output(file).unwrap();
stream.write_all(&b).unwrap(); stream.write_all(&b).unwrap();
stream.finish().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(); 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::<Wallet, bincode::config::Configuration>(&buf, bincode::config::Configuration::default()).unwrap(); let (wallet, _): (Wallet, usize) = bincode::serde::decode_from_slice::<Wallet, bincode::config::Configuration>(&buf, bincode::config::standard()).unwrap();
dbg!(&wallet); dbg!(&wallet);
} }

2
wallet/Cargo.lock generated
View File

@ -1693,8 +1693,10 @@ name = "wallet"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"age", "age",
"bincode",
"hex", "hex",
"k256", "k256",
"serde",
"sha3", "sha3",
"shared", "shared",
"thiserror 2.0.16", "thiserror 2.0.16",

View File

@ -5,8 +5,10 @@ edition = "2024"
[dependencies] [dependencies]
age = { version = "0.11.1", features = ["cli-common"] } age = { version = "0.11.1", features = ["cli-common"] }
bincode = { version = "2.0.1", features = ["serde"] }
hex = "0.4.3" 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" sha3 = "0.10.8"
shared = { path = "../shared" } shared = { path = "../shared" }
thiserror = "2.0.16" thiserror = "2.0.16"

View File

@ -1,3 +1,5 @@
use age::Identity;
use bincode::{Decode, Encode};
use k256::sha2::Digest; use k256::sha2::Digest;
use k256::ecdsa::{ use k256::ecdsa::{
self, self,
@ -5,13 +7,17 @@ use k256::ecdsa::{
VerifyingKey, VerifyingKey,
RecoveryId, RecoveryId,
Signature, Signature,
signature::Verifier,
}; };
use shared::core::{ Transaction, SignedTransaction, Address, }; use shared::core::{ Transaction, SignedTransaction, Address, };
use k256::elliptic_curve::rand_core::OsRng; use k256::elliptic_curve::rand_core::OsRng;
use sha3::Keccak256; use sha3::Keccak256;
use std::io::Read;
use std::path; use std::path;
static BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard();
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum WalletError { pub enum WalletError {
#[error("No Private Key present in Wallet")] #[error("No Private Key present in Wallet")]
@ -20,6 +26,12 @@ pub enum WalletError {
#[error("Signature error: {0}")] #[error("Signature error: {0}")]
SignatureError(#[from] ecdsa::Error), 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}")] #[error("Provided Recovery ID is invalid: {0}")]
InvalidRecoveryId(u8), InvalidRecoveryId(u8),
@ -27,26 +39,39 @@ pub enum WalletError {
IO(#[from] std::io::Error), IO(#[from] std::io::Error),
#[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 { pub struct Wallet {
address: Address, address: Address,
balance: u64, balance: u64,
nonce: u64, nonce: u64,
private_key: Option<SigningKey>, private_key: [u8; 32],
} }
impl Wallet { impl Wallet {
fn verify_signature(tx: &SignedTransaction) -> Result<VerifyingKey, WalletError>{ fn verify_self_signature(&self, sign_tx: &SignedTransaction) -> Result<(), WalletError>{
if let Some(rec_id) = RecoveryId::from_byte(tx.recovery_id()) { if let Some(rec_id) = RecoveryId::from_byte(sign_tx.recovery_id()) {
let sig = Signature::from_slice(tx.signature())?; let sig = Signature::from_slice(sign_tx.signature())?;
let hash = tx.tx().hash(); println!("Signature recovered");
Ok(VerifyingKey::recover_from_prehash(&hash, &sig, rec_id)?) 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 { } else {
Err(WalletError::InvalidRecoveryId(tx.recovery_id())) Err(WalletError::InvalidRecoveryId(sign_tx.recovery_id()))
} }
} }
@ -54,8 +79,8 @@ impl Wallet {
SigningKey::random(&mut OsRng) SigningKey::random(&mut OsRng)
} }
pub fn address(&self) -> &[u8] { pub fn address(&self) -> [u8; 20] {
&self.address self.address.clone()
} }
pub fn nonce(&self) -> u64 { pub fn nonce(&self) -> u64 {
@ -63,15 +88,15 @@ impl Wallet {
} }
pub fn public_key(&self) -> Result<VerifyingKey, WalletError> { pub fn public_key(&self) -> Result<VerifyingKey, WalletError> {
if let Some(pk) = &self.private_key { let pk = SigningKey::from_bytes(&self.private_key.into())?;
Ok(*pk.verifying_key()) Ok(*pk.verifying_key())
} else {
Err(WalletError::NoPrivateKeyProvided)
}
} }
pub fn verifying_key_to_address(private_key: &SigningKey) -> Address { pub fn private_key(&self) -> Result<SigningKey, WalletError> {
let public_key = private_key.verifying_key(); 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.to_encoded_point(false);
let public_key_bytes = public_key_bytes.as_bytes(); let public_key_bytes = public_key_bytes.as_bytes();
@ -83,44 +108,54 @@ impl Wallet {
} }
fn load(path: path::PathBuf) -> Result<Self, WalletError> { fn load(path: path::PathBuf) -> Result<Self, WalletError> {
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<SigningKey>) -> Self { fn new() -> Self {
let address = if let Some(pk) = &pk { let pk = Wallet::generate_private_key();
Self::verifying_key_to_address(pk) let address = Self::public_key_to_address(&pk.verifying_key());
} else {
Address::default()
};
Self { Self {
nonce: 0, nonce: 0,
balance: 0, balance: 0,
address, address,
private_key: pk private_key: pk.to_bytes().into()
} }
} }
pub fn sign(&self, transaction: Transaction) -> Result<SignedTransaction, WalletError> { pub fn sign(&self, transaction: Transaction) -> Result<SignedTransaction, WalletError> {
let hash = transaction.hash(); let hash = transaction.hash();
if let Some(pk) = &self.private_key { let pk = SigningKey::from_bytes(&self.private_key.into())?;
let (signature, recovery_id) = pk.sign_prehash_recoverable(&hash)?; let (signature, recovery_id) = pk.sign_prehash_recoverable(&hash)?;
Ok(SignedTransaction::new( Ok(SignedTransaction::new(
transaction, transaction,
signature.to_bytes().into(), signature.to_bytes().into(),
recovery_id.into() recovery_id.into()
)) ))
} else {
Err(WalletError::NoPrivateKeyProvided)
}
} }
} }
#[test] #[test]
fn acc_new_sign_no_key() { fn acc_new_sign_no_key() {
let wallet = Wallet::new(None); let wallet = Wallet::new();
let to_address: [u8; 20] = [1u8; 20]; let to_address: [u8; 20] = [1u8; 20];
let mut wallet_addr = [0u8; 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( let tx = Transaction::new(
wallet_addr, wallet_addr,
to_address, to_address,
@ -132,30 +167,64 @@ fn acc_new_sign_no_key() {
assert!(matches!(ret, Err(WalletError::NoPrivateKeyProvided))) assert!(matches!(ret, Err(WalletError::NoPrivateKeyProvided)))
} }
#[test] #[cfg(test)]
fn acc_new_sign_with_key() -> Result<(), WalletError> { mod test {
let pk = Wallet::generate_private_key(); const WALLET_PATH: &str = "./wallet.age";
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)?;
let orig_pub_key_bytes = orig_public_key.to_encoded_point(false); use std::io::Write;
let new_pub_key_bytes = pub_key.to_encoded_point(false);
assert_eq!(orig_pub_key_bytes, new_pub_key_bytes); 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, _>(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(()) 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(())
}
}
} }

BIN
wallet/wallet.age Normal file

Binary file not shown.