diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index aea82b9..90bd34f 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -11,6 +11,7 @@ program-methods = { path = "program_methods" } serde = "1.0.219" serde_cbor = "0.11.2" sha2 = "0.10.9" +secp256k1 = "0.31.1" [dev-dependencies] test-program-methods = { path = "test_program_methods" } diff --git a/nssa/src/address.rs b/nssa/src/address.rs index c5f37a6..a8a9634 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -14,7 +14,7 @@ impl Address { pub fn from_public_key(public_key: &PublicKey) -> Self { // TODO: implement - Address::new([public_key.0; 32]) + Address::new(public_key.0) } pub fn value(&self) -> &[u8; 32] { diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 082f108..c4c96c7 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -13,4 +13,7 @@ pub enum NssaError { #[error("Serialization error: {0}")] InstructionSerializationError(String), + + #[error("Invalid private key")] + InvalidPrivateKey, } diff --git a/nssa/src/program.rs b/nssa/src/program.rs index c62310e..b1ad8ca 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -21,8 +21,7 @@ impl Program { pub fn serialize_instruction( instruction: T, ) -> Result { - to_vec(&instruction) - .map_err(|e| NssaError::InstructionSerializationError(e.to_string())) + to_vec(&instruction).map_err(|e| NssaError::InstructionSerializationError(e.to_string())) } pub(crate) fn execute( diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index d5d5aea..61e1683 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -7,9 +7,39 @@ pub struct WitnessSet { pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>, } -fn message_to_bytes(_message: &Message) -> Vec { - //TODO: implement - vec![0, 0] +const MESSAGE_ENCODING_PREFIX: &[u8; 19] = b"NSSA/v0.1/TxMessage"; + +/// Serializes a `Message` into bytes in the following layout: +/// TAG || (bytes LE) * 8 || addresses_len (4 bytes LE) || addresses (32 bytes * N) || nonces_len (4 bytes LE) || nonces (16 bytes * M) || instruction_data_len || instruction_data (4 bytes * K) +/// Integers and words are encoded in little-endian byte order, and fields appear in the above order. +fn message_to_bytes(message: &Message) -> Vec { + let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec(); + // program_id: [u32; 8] + for word in &message.program_id { + bytes.extend_from_slice(&word.to_le_bytes()); + } + // addresses: Vec<[u8;32]> + // serialize length as u32 little endian, then all addresses concatenated + let addresses_len = message.addresses.len() as u32; + bytes.extend(&addresses_len.to_le_bytes()); + for addr in &message.addresses { + bytes.extend_from_slice(addr.value()); + } + // nonces: Vec + let nonces_len = message.nonces.len() as u32; + bytes.extend(&nonces_len.to_le_bytes()); + for nonce in &message.nonces { + bytes.extend(&nonce.to_le_bytes()); + } + // instruction_data: Vec + // serialize length as u32 little endian, then all addresses concatenated + let instr_len = message.instruction_data.len() as u32; + bytes.extend(&instr_len.to_le_bytes()); + for word in &message.instruction_data { + bytes.extend(&word.to_le_bytes()); + } + + bytes } impl WitnessSet { diff --git a/nssa/src/signature.rs b/nssa/src/signature.rs index 9e36a7c..02fe7b3 100644 --- a/nssa/src/signature.rs +++ b/nssa/src/signature.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::public_transaction::Message; +use crate::{error::NssaError, public_transaction::Message}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Signature(pub(crate) u8); @@ -9,32 +9,46 @@ pub struct Signature(pub(crate) u8); // TODO: Remove Debug, Clone, Serialize, Deserialize, PartialEq and Eq for security reasons // TODO: Implement Zeroize #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct PrivateKey(pub(crate) u8); +pub struct PrivateKey(pub(crate) [u8; 32]); impl PrivateKey { - pub fn new(dummy_value: u8) -> Self { - Self(dummy_value) + fn is_valid_key(value: [u8; 32]) -> bool { + secp256k1::SecretKey::from_byte_array(value).is_ok() + } + + pub fn try_new(value: [u8; 32]) -> Result { + if Self::is_valid_key(value) { + Ok(Self(value)) + } else { + Err(NssaError::InvalidPrivateKey) + } } } // TODO: Dummy impl. Replace by actual public key. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct PublicKey(pub(crate) u8); +pub struct PublicKey(pub(crate) [u8; 32]); impl PublicKey { pub fn new(key: &PrivateKey) -> Self { - // TODO: implement - Self(key.0) + let value = { + let secret_key = secp256k1::SecretKey::from_byte_array(key.0).unwrap(); + let public_key = + secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key); + let (x_only, _) = public_key.x_only_public_key(); + x_only.serialize() + }; + Self(value) } } impl Signature { pub(crate) fn new(key: &PrivateKey, _message: &[u8]) -> Self { - Signature(key.0) + Signature(0) } - pub fn is_valid_for(&self, _message: &Message, public_key: &PublicKey) -> bool { + pub fn is_valid_for(&self, _message: &Message, _public_key: &PublicKey) -> bool { // TODO: implement - self.0 == public_key.0 + true } } diff --git a/nssa/src/tests/state_tests.rs b/nssa/src/tests/state_tests.rs index 7b20f7d..c0745f7 100644 --- a/nssa/src/tests/state_tests.rs +++ b/nssa/src/tests/state_tests.rs @@ -1,6 +1,6 @@ use crate::{ - Address, PublicTransaction, V01State, error::NssaError, program::Program, public_transaction, - signature::PrivateKey, + Address, PublicKey, PublicTransaction, V01State, error::NssaError, program::Program, + public_transaction, signature::PrivateKey, }; use nssa_core::account::Account; @@ -14,22 +14,24 @@ fn transfer_transaction( let addresses = vec![from, to]; let nonces = vec![nonce]; let program_id = Program::authenticated_transfer_program().id(); - let message = public_transaction::Message::try_new(program_id, addresses, nonces, balance).unwrap(); + let message = + public_transaction::Message::try_new(program_id, addresses, nonces, balance).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); PublicTransaction::new(message, witness_set) } #[test] fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { - let initial_data = [([1; 32], 100)]; + let key = PrivateKey::try_new([1; 32]).unwrap(); + let address = Address::from_public_key(&PublicKey::new(&key)); + let initial_data = [(*address.value(), 100)]; let mut state = V01State::new_with_genesis_accounts(&initial_data); - let from = Address::new(initial_data[0].0); - let from_key = PrivateKey(1); + let from = address; let to = Address::new([2; 32]); assert_eq!(state.get_account_by_address(&to), Account::default()); let balance_to_move = 5; - let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); + let tx = transfer_transaction(from.clone(), key, 0, to.clone(), balance_to_move); state.transition_from_public_transaction(&tx).unwrap(); assert_eq!(state.get_account_by_address(&from).balance, 95); @@ -40,10 +42,12 @@ fn transition_from_authenticated_transfer_program_invocation_default_account_des #[test] fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { - let initial_data = [([1; 32], 100)]; + let key = PrivateKey::try_new([1; 32]).unwrap(); + let address = Address::from_public_key(&PublicKey::new(&key)); + let initial_data = [(*address.value(), 100)]; let mut state = V01State::new_with_genesis_accounts(&initial_data); - let from = Address::new(initial_data[0].0); - let from_key = PrivateKey(1); + let from = address; + let from_key = key; let to = Address::new([2; 32]); let balance_to_move = 101; assert!(state.get_account_by_address(&from).balance < balance_to_move); @@ -60,11 +64,15 @@ fn transition_from_authenticated_transfer_program_invocation_insuficient_balance #[test] fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() { - let initial_data = [([1; 32], 100), ([99; 32], 200)]; + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let address1 = Address::from_public_key(&PublicKey::new(&key1)); + let address2 = Address::from_public_key(&PublicKey::new(&key2)); + let initial_data = [(*address1.value(), 100), (*address2.value(), 200)]; let mut state = V01State::new_with_genesis_accounts(&initial_data); - let from = Address::new(initial_data[1].0); - let from_key = PrivateKey(99); - let to = Address::new(initial_data[0].0); + let from = address2; + let from_key = key2; + let to = address1; assert_ne!(state.get_account_by_address(&to), Account::default()); let balance_to_move = 8; @@ -79,37 +87,25 @@ fn transition_from_authenticated_transfer_program_invocation_non_default_account #[test] fn transition_from_chained_authenticated_transfer_program_invocations() { - let initial_data = [([1; 32], 100)]; + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let address1 = Address::from_public_key(&PublicKey::new(&key1)); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let address2 = Address::from_public_key(&PublicKey::new(&key2)); + let initial_data = [(*address1.value(), 100)]; let mut state = V01State::new_with_genesis_accounts(&initial_data); - let address_1 = Address::new(initial_data[0].0); - let key_1 = PrivateKey(1); - let address_2 = Address::new([2; 32]); - let key_2 = PrivateKey(2); - let address_3 = Address::new([3; 32]); + let address3 = Address::new([3; 32]); let balance_to_move = 5; - let tx = transfer_transaction( - address_1.clone(), - key_1, - 0, - address_2.clone(), - balance_to_move, - ); + let tx = transfer_transaction(address1.clone(), key1, 0, address2.clone(), balance_to_move); state.transition_from_public_transaction(&tx).unwrap(); let balance_to_move = 3; - let tx = transfer_transaction( - address_2.clone(), - key_2, - 0, - address_3.clone(), - balance_to_move, - ); + let tx = transfer_transaction(address2.clone(), key2, 0, address3.clone(), balance_to_move); state.transition_from_public_transaction(&tx).unwrap(); - assert_eq!(state.get_account_by_address(&address_1).balance, 95); - assert_eq!(state.get_account_by_address(&address_2).balance, 2); - assert_eq!(state.get_account_by_address(&address_3).balance, 3); - assert_eq!(state.get_account_by_address(&address_1).nonce, 1); - assert_eq!(state.get_account_by_address(&address_2).nonce, 1); - assert_eq!(state.get_account_by_address(&address_3).nonce, 0); + assert_eq!(state.get_account_by_address(&address1).balance, 95); + assert_eq!(state.get_account_by_address(&address2).balance, 2); + assert_eq!(state.get_account_by_address(&address3).balance, 3); + assert_eq!(state.get_account_by_address(&address1).nonce, 1); + assert_eq!(state.get_account_by_address(&address2).nonce, 1); + assert_eq!(state.get_account_by_address(&address3).nonce, 0); } diff --git a/nssa/src/tests/valid_execution_tests.rs b/nssa/src/tests/valid_execution_tests.rs index 8870cdd..399c47a 100644 --- a/nssa/src/tests/valid_execution_tests.rs +++ b/nssa/src/tests/valid_execution_tests.rs @@ -61,7 +61,8 @@ fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_prog assert_eq!(account.nonce, Account::default().nonce); assert_eq!(account.data, Account::default().data); let program_id = Program::program_owner_changer().id(); - let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); + let message = + public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -84,7 +85,8 @@ fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_bala assert_eq!(account.nonce, Account::default().nonce); assert_eq!(account.data, Account::default().data); let program_id = Program::program_owner_changer().id(); - let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); + let message = + public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -107,7 +109,8 @@ fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonc assert_ne!(account.nonce, Account::default().nonce); assert_eq!(account.data, Account::default().data); let program_id = Program::program_owner_changer().id(); - let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); + let message = + public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -130,7 +133,8 @@ fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data assert_eq!(account.nonce, Account::default().nonce); assert_ne!(account.data, Account::default().data); let program_id = Program::program_owner_changer().id(); - let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); + let message = + public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -156,7 +160,8 @@ fn test_program_should_fail_if_transfers_balance_from_non_owned_account() { vec![sender_address, receiver_address], vec![], balance_to_move, - ).unwrap(); + ) + .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -178,7 +183,8 @@ fn test_program_should_fail_if_modifies_data_of_non_owned_account() { state.get_account_by_address(&address).program_owner, program_id ); - let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); + let message = + public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -194,7 +200,8 @@ fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() { let address = Address::new([1; 32]); let program_id = Program::minter().id(); - let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); + let message = + public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -219,7 +226,8 @@ fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() { assert!(state.get_account_by_address(&address).balance > balance_to_burn); let message = - public_transaction::Message::try_new(program_id, vec![address], vec![], balance_to_burn).unwrap(); + public_transaction::Message::try_new(program_id, vec![address], vec![], balance_to_burn) + .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); let result = state.transition_from_public_transaction(&tx);