This commit is contained in:
Victor Vobis 2025-09-05 17:39:37 +02:00
parent 5b9834519d
commit 891f93de6b
13 changed files with 2576 additions and 129 deletions

View File

@ -44,7 +44,7 @@ pub enum CliCommand {
/// Make a Transaction
#[command(name = "tx")]
Transaction(core::Tx),
Transaction(core::Transaction),
/// Start new TcpListner on Addr
#[command(name = "listen")]

View File

@ -29,7 +29,7 @@ pub enum BlockchainError {
InvalidAccountCreation,
#[error("Transactional error")]
Tx(#[from] shared::core::TxError),
Transaction(#[from] shared::core::TransactionError),
#[error("Validation Error")]
Validation(#[from] ValidationError),
@ -109,7 +109,7 @@ impl Blockchain {
}
}
fn apply_transaction(&mut self, tx: &core::Tx) -> Result<(), BlockchainError> {
fn apply_transaction(&mut self, tx: &core::Transaction) -> Result<(), BlockchainError> {
tx.validate()?;
let from = tx.from();
let to = tx.to();

View File

@ -1,8 +1,8 @@
use bincode::{Decode, Encode};
use super::Tx;
use super::Transaction;
#[derive(serde::Deserialize, serde::Serialize, Encode, Decode, Debug, Clone)]
pub enum ChainData {
Transaction(Tx),
Transaction(Transaction),
}

View File

@ -6,7 +6,7 @@ use super::{BlockHeader, ChainData};
pub struct Hasher {}
impl Hasher {
pub fn hash_chain_data(data: &ChainData) -> String {
pub fn hash_chain_data(data: &ChainData) -> [u8; 32] {
let mut hasher = Sha256::new();
match data {
ChainData::Transaction(tx) => {
@ -17,7 +17,7 @@ impl Hasher {
}
}
let res = hasher.finalize();
hex::encode(res)
res.into()
}
pub fn calculate_next_level(level: &[String]) -> Vec<String> {

View File

@ -1,5 +1,4 @@
use super::Address;
use crate::log;
use thiserror::Error;
@ -11,9 +10,37 @@ use thiserror::Error;
//use ring::digest;
//
#[derive(Clone)]
pub struct AddressParser {}
impl clap::builder::TypedValueParser for AddressParser {
type Value = [u8; 20];
fn parse_ref(
&self,
_cmd: &clap::Command,
_arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let str = value.to_str().ok_or_else(|| {
clap::Error::new(clap::error::ErrorKind::InvalidValue)
})?;
let stripped_value = str.strip_prefix("0x").unwrap_or(str);
let bytes = hex::decode(stripped_value).map_err(|_| {
clap::Error::new(clap::error::ErrorKind::InvalidValue)
})?;
let mut addr = [0u8; 20];
addr.copy_from_slice(&bytes[..12]);
Ok(addr)
}
}
#[allow(dead_code)]
#[derive(Error, Debug)]
pub enum TxError {
pub enum TransactionError {
#[error("from field is empty")]
FromEmpty,
#[error("to field is empty")]
@ -29,30 +56,47 @@ pub enum TxError {
#[derive(
serde::Deserialize, serde::Serialize, Debug, clap::Args, Clone, bincode::Encode, bincode::Decode,
)]
pub struct Tx {
pub struct Transaction {
#[clap(value_parser = AddressParser {})]
from: Address,
#[clap(value_parser = AddressParser {})]
to: Address,
value: u64,
data: String,
nonce: u64,
}
#[derive(Debug, Clone)]
pub struct SignedTransaction {
tx: Tx,
signature: [u8; 32],
tx: Transaction,
signature: [u8; 64],
recovery_id: u8,
}
/// Takes a [u8; 32] as arg for signature
impl SignedTransaction {
pub fn new(tx: Tx, signature: [u8; 32]) -> Self {
pub fn new(tx: Transaction, signature: [u8; 64], recovery_id: u8) -> Self {
Self {
tx,
signature
signature,
recovery_id,
}
}
pub fn signature(&self) -> &[u8] {
&self.signature
}
pub fn recovery_id(&self) -> u8 {
self.recovery_id
}
pub fn tx(&self) -> &Transaction {
&self.tx
}
}
impl Tx {
impl Transaction {
pub fn new(from: Address, to: Address, value: u64, nonce: u64, data: String) -> Self {
Self {
from,
@ -63,30 +107,33 @@ impl Tx {
}
}
pub fn validate(&self) -> Result<(), TxError> {
pub fn validate(&self) -> Result<(), TransactionError> {
if self.from.is_empty() {
return Err(TxError::FromEmpty);
return Err(TransactionError::FromEmpty);
} else if self.to.is_empty() {
return Err(TxError::ToEmpty);
return Err(TransactionError::ToEmpty);
} else if self.value == 0 {
return Err(TxError::ValueEmpty);
return Err(TransactionError::ValueEmpty);
}
Ok(())
}
pub fn hash(&self) -> String {
pub fn hash(&self) -> [u8; 32] {
super::Hasher::hash_chain_data(&super::ChainData::Transaction(self.clone()))
}
pub fn from(&self) -> &Address {
&self.from
}
pub fn to(&self) -> &Address {
&self.to
}
pub fn value(&self) -> u64 {
self.value
}
pub fn data(&self) -> &str {
&self.data
}

1203
testing/keys/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,9 @@ version = "0.1.0"
edition = "2024"
[dependencies]
age = { version = "0.11.1", features = ["cli-common"] }
bincode = { version = "2.0.1", features = ["serde"] }
pkcs8 = { version = "0.10.2", features = ["encryption", "std", "alloc", "pkcs5"] }
rand = { version = "0.9.2", features = ["thread_rng"] }
secp256k1 = { version = "0.31.1", features = ["hashes", "rand", "serde"] }
serde = { version = "1.0.219", features = ["derive"] }

5
testing/keys/crypt.age Normal file
View File

@ -0,0 +1,5 @@
age-encryption.org/v1
-> scrypt FP9Jzf9WMGgQj2HZPAbuDw 14
i+HlcYKckQXUKAtoY8SIjJUz15IE2GucgQM0sZxLx78
--- 5JVy3rsJpvCXTl41B6/k/aC0HqoAdyfH4I6efffwr/w
-éÛÂ|Q÷ÚúÇ‘-D‰3´<%’¥|~<7E>¨˜<>韥<C5B8>)êÀÂ"ŠÈ+ùñá(Ýþ°}<7D>ÂéëKüv”É<>'#<23>ƒ°S<>tUuîX,’ù|‡ ïŒÝZ>CËõÐ šó•Ú:Y”#úÔ:Á<>×Ô•Þã LNM¼ô“ËìÕ Ö*-´¸ èôãHnDˆ²£ÑU«OcÚoùJ×þ£^å³c¸òŽý«¦X˜0ؼp¸K\’š—ê~âuX|Ì; |ßÎ7 D¸MÄD‡;_Ÿlõ`K†^Ž+ìAË

6
testing/keys/out.crypt Normal file
View File

@ -0,0 +1,6 @@
age-encryption.org/v1
-> scrypt FEp9+A7Sl9E295JnlNQDaQ 14
i9VIye4v87oaN/J/Wgp+9r8PCoqrj1230s4hobeYFn0
--- TYrKOs0QpxkYvblubIUXh3mkgpo8Fs0P7yI3SWl0Dik
J/—*•ÓPÍ>Äå¨KßýWÓ<1B>@û4£c*~—C.糉‡J„(õNÀ

View File

@ -1,59 +1,74 @@
use pkcs8::der::Decode;
use pkcs8::{EncryptedPrivateKeyInfo, PrivateKeyInfo};
use pkcs8::pkcs5::scrypt::Params;
use secp256k1::{Secp256k1, SecretKey};
use rand::{rng, RngCore};
//
// OID for secp256k1 curve
const SECP256K1_OID: pkcs8::ObjectIdentifier = pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.10");
fn generate_salt() -> [u8; 16] {
let mut salt = [0u8; 16];
rng().fill_bytes(&mut salt);
salt
}
fn main() {
let secp = Secp256k1::new();
let (secret_key, public_key) = secp.generate_keypair(&mut rand::rng());
let password = "test";
let secret_bytes = &secret_key.secret_bytes();
let pk_info = PrivateKeyInfo::new(
pkcs8::AlgorithmIdentifierRef {
oid: SECP256K1_OID,
parameters: None,
},
secret_bytes
);
println!("private key length: {}", secret_bytes.len());
let salt = generate_salt();
let enc_priv = pk_info.encrypt_with_params(
pkcs8::pkcs5::pbes2::Parameters::scrypt_aes256cbc(
Params::new(15, 8, 1, secret_bytes.len()).unwrap(),
&salt,
&salt
).unwrap(),
password
).unwrap();
&enc_priv.write_der_file("./out.der").unwrap();
let buf = std::fs::read("./out.der").unwrap();
let enc_key = EncryptedPrivateKeyInfo::from_der(buf.as_slice()).unwrap();
let dec_doc = enc_key.decrypt(password.as_bytes()).unwrap();
let dec_key = PrivateKeyInfo::from_der(dec_doc.as_bytes()).unwrap();
dbg!(&dec_key);
let sec_key_bytes = dec_key.private_key;
let mut sec_key_bytes = [0u8; 32];
sec_key_bytes.copy_from_slice(dec_key.private_key);
let sec_key = SecretKey::from_byte_array(sec_key_bytes);
println!("original key: {:#?}", secret_key);
println!("decrypted key: {:#?}", sec_key);
fn main() {}
#[cfg(test)]
mod tests {
const OUT_FILE: &str = "crypt.age";
#[derive(Debug, bincode::Encode, bincode::Decode, serde::Serialize, serde::Deserialize)]
pub struct Wallet {
address: String,
balance: u64,
nonce: u64,
private_key: String,
}
use std::io::{ Read, Write };
fn encrypt() {
let passphrase = age::secrecy::SecretString::from("password");
let wallet = Wallet {
address: "My home address".to_string(),
balance: 500,
nonce: 3,
private_key: "thisisprivate".to_string()
};
let b = bincode::serde::encode_to_vec::<Wallet, bincode::config::Configuration>(wallet, bincode::config::Configuration::default()).unwrap();
let encryptor = age::Encryptor::with_user_passphrase(passphrase);
let file = std::fs::OpenOptions::new().create(true).write(true).open(OUT_FILE).unwrap();
let mut stream = encryptor.wrap_output(file).unwrap();
stream.write_all(&b).unwrap();
stream.finish().unwrap();
}
#[test]
fn decrypt() {
let passphrase = age::secrecy::SecretString::from("password");
let identity = age::scrypt::Identity::new(passphrase);
let file = std::fs::OpenOptions::new().read(true).open(OUT_FILE).unwrap();
let decryptor = age::Decryptor::new(file).unwrap();
let mut buf = Vec::new();
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();
dbg!(&wallet);
}
#[test]
fn io_decrypt() {
let passphrase = age::cli_common::read_secret("Unlock you file", "Please Enter you passphrase", Some("confirm?")).unwrap();
let identity = age::scrypt::Identity::new(passphrase);
let file = std::fs::OpenOptions::new().create(true).read(true).open("crypt.age").unwrap();
let decryptor = age::Decryptor::new(file).unwrap();
let mut buf = Vec::new();
decryptor.decrypt(std::iter::once(&identity as &dyn age::Identity)).unwrap().read_to_end(&mut buf).unwrap();
let (b, _) = bincode::decode_from_slice::<Vec<u8>, bincode::config::Configuration>(&buf, bincode::config::Configuration::default()).unwrap();
let decrypt = age::decrypt(&identity, &b).unwrap();
assert_eq!(b"Hello World", decrypt.as_slice());
}
}

1121
wallet/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,9 @@ version = "0.1.0"
edition = "2024"
[dependencies]
age = { version = "0.11.1", features = ["cli-common"] }
hex = "0.4.3"
k256 = { version = "0.13.4", features = ["serde"] }
sha3 = "0.10.8"
shared = { path = "../shared" }
thiserror = "2.0.16"

View File

@ -1,4 +1,4 @@
use k256::sha2::{Sha256, Digest};
use k256::sha2::Digest;
use k256::ecdsa::{
self,
SigningKey,
@ -6,38 +6,70 @@ use k256::ecdsa::{
RecoveryId,
Signature,
};
use shared::core::Address;
use shared::core::{ Transaction, SignedTransaction, Address, };
use k256::elliptic_curve::rand_core::OsRng;
use sha3::Keccak256;
use shared::core::{ Tx, SignedTransaction };
use std::path;
#[derive(Debug, thiserror::Error)]
pub enum WalletError {
#[error("No Private Key present in Wallet")]
NoPrivateKeyProvided,
#[error("Signature error: {0}")]
SignatureError(#[from] ecdsa::Error),
#[error("Provided Recovery ID is invalid: {0}")]
InvalidRecoveryId(u8),
#[error("I/O error: {0}")]
IO(#[from] std::io::Error),
#[error("")]
InvalidHashLength
}
#[derive(Debug)]
pub struct Wallet {
address: Address,
balance: u64,
nonce: u64,
private_key: SigningKey,
}
fn sign_key_test() -> Result<(Signature, RecoveryId), ecdsa::Error> {
let signing_key = SigningKey::random(&mut OsRng);
let message = b"ECDSA message";
let hash = Keccak256::digest(message);
signing_key.sign_prehash_recoverable(&hash)
}
fn verify_message(
message: &[u8; 32],
signature: &Signature,
recovery_id: &RecoveryId
) -> Result<VerifyingKey, ecdsa::Error>{
VerifyingKey::recover_from_prehash(message, signature, *recovery_id)
private_key: Option<SigningKey>,
}
impl Wallet {
fn verify_signature(tx: &SignedTransaction) -> Result<VerifyingKey, WalletError>{
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)?)
} else {
Err(WalletError::InvalidRecoveryId(tx.recovery_id()))
}
}
pub fn generate_private_key() -> SigningKey {
SigningKey::random(&mut OsRng)
}
pub fn address(&self) -> &[u8] {
&self.address
}
pub fn nonce(&self) -> u64 {
self.nonce
}
pub fn public_key(&self) -> Result<VerifyingKey, WalletError> {
if let Some(pk) = &self.private_key {
Ok(*pk.verifying_key())
} else {
Err(WalletError::NoPrivateKeyProvided)
}
}
pub fn verifying_key_to_address(private_key: &SigningKey) -> Address {
let public_key = private_key.verifying_key();
let public_key_bytes = public_key.to_encoded_point(false);
@ -45,32 +77,85 @@ impl Wallet {
let hash = Keccak256::digest(&public_key_bytes[1..]);
let mut address: Address;
let mut address: Address = [0; 20];
address.copy_from_slice(&hash[12..]);
address
}
pub fn address_from_pubkey(key: VerifyingKey) -> Address {
let addr = key.to_encoded_point(true);
fn load(path: path::PathBuf) -> Result<Self, WalletError> {
let content = std::fs::read(path)?;
}
fn new() -> Self {
let key = SigningKey::random(&mut OsRng);
let address = Self::verifying_key_to_address(&key);
fn new(pk: Option<SigningKey>) -> Self {
let address = if let Some(pk) = &pk {
Self::verifying_key_to_address(pk)
} else {
Address::default()
};
Self {
nonce: 0,
balance: 0,
address,
private_key: key
private_key: pk
}
}
pub fn sign(&self, transaction: Tx) -> SignedTransaction {
pub fn sign(&self, transaction: Transaction) -> Result<SignedTransaction, WalletError> {
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)
}
}
}
#[test]
fn test_acc_new() {
Wallet::new();
let (k, r) = sign_key_test().unwrap();
fn acc_new_sign_no_key() {
let wallet = Wallet::new(None);
let to_address: [u8; 20] = [1u8; 20];
let mut wallet_addr = [0u8; 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, 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)?;
let orig_pub_key_bytes = orig_public_key.to_encoded_point(false);
let new_pub_key_bytes = pub_key.to_encoded_point(false);
assert_eq!(orig_pub_key_bytes, new_pub_key_bytes);
Ok(())
}