From 504eb008191db7b6a479a9fe2f2fafb6b80b02b6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 13 Aug 2025 01:33:11 -0300 Subject: [PATCH] refactor tests --- accounts/src/account_core/mod.rs | 4 +- accounts/src/key_management/mod.rs | 2 +- nssa/src/address.rs | 4 +- nssa/src/error.rs | 3 + nssa/src/lib.rs | 2 - nssa/src/program.rs | 131 ++++- nssa/src/public_transaction/encoding.rs | 2 +- nssa/src/public_transaction/mod.rs | 1 + nssa/src/public_transaction/transaction.rs | 203 ++++++- nssa/src/public_transaction/witness_set.rs | 2 +- .../bip340_test_vectors.rs | 108 ++-- nssa/src/signature/encoding.rs | 4 +- nssa/src/signature/mod.rs | 50 +- nssa/src/signature/private_key.rs | 6 +- nssa/src/signature/public_key.rs | 39 +- nssa/src/state.rs | 498 +++++++++++++++++- nssa/src/tests/mod.rs | 6 - nssa/src/tests/program_tests.rs | 123 ----- nssa/src/tests/public_transaction_tests.rs | 195 ------- nssa/src/tests/signature_tests.rs | 53 -- nssa/src/tests/state_tests.rs | 198 ------- nssa/src/tests/valid_execution_tests.rs | 289 ---------- 22 files changed, 981 insertions(+), 942 deletions(-) rename nssa/src/{tests => signature}/bip340_test_vectors.rs (86%) delete mode 100644 nssa/src/tests/mod.rs delete mode 100644 nssa/src/tests/program_tests.rs delete mode 100644 nssa/src/tests/public_transaction_tests.rs delete mode 100644 nssa/src/tests/signature_tests.rs delete mode 100644 nssa/src/tests/state_tests.rs delete mode 100644 nssa/src/tests/valid_execution_tests.rs diff --git a/accounts/src/account_core/mod.rs b/accounts/src/account_core/mod.rs index 58154f0..3d7fe88 100644 --- a/accounts/src/account_core/mod.rs +++ b/accounts/src/account_core/mod.rs @@ -117,7 +117,7 @@ impl AccountPublicMask { impl Account { pub fn new() -> Self { let key_holder = AddressKeyHolder::new_os_random(); - let public_key = nssa::PublicKey::new(key_holder.get_pub_account_signing_key()); + let public_key = nssa::PublicKey::new_from_private_key(key_holder.get_pub_account_signing_key()); let address = nssa::Address::from_public_key(&public_key); let balance = 0; let utxos = HashMap::new(); @@ -132,7 +132,7 @@ impl Account { pub fn new_with_balance(balance: u64) -> Self { let key_holder = AddressKeyHolder::new_os_random(); - let public_key = nssa::PublicKey::new(key_holder.get_pub_account_signing_key()); + let public_key = nssa::PublicKey::new_from_private_key(key_holder.get_pub_account_signing_key()); let address = nssa::Address::from_public_key(&public_key); let utxos = HashMap::new(); diff --git a/accounts/src/key_management/mod.rs b/accounts/src/key_management/mod.rs index e8444e7..a9cb196 100644 --- a/accounts/src/key_management/mod.rs +++ b/accounts/src/key_management/mod.rs @@ -341,7 +341,7 @@ mod tests { } }; - let public_key = nssa::PublicKey::new(&pub_account_signing_key); + let public_key = nssa::PublicKey::new_from_private_key(&pub_account_signing_key); let address = nssa::Address::from_public_key(&public_key); diff --git a/nssa/src/address.rs b/nssa/src/address.rs index eeb9102..4aa4c45 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -4,7 +4,7 @@ use crate::signature::PublicKey; #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct Address { - pub(crate) value: [u8; 32], + value: [u8; 32], } impl Address { @@ -14,7 +14,7 @@ impl Address { pub fn from_public_key(public_key: &PublicKey) -> Self { // TODO: Check specs - Address::new(public_key.0) + Address::new(*public_key.value()) } pub fn value(&self) -> &[u8; 32] { diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 5e173b5..11a2f41 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -21,4 +21,7 @@ pub enum NssaError { #[error("IO error: {0}")] Io(#[from] io::Error), + + #[error("Invalid Public Key")] + InvalidPublicKey, } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index bde852c..bcf536e 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -12,5 +12,3 @@ pub use signature::PublicKey; pub use signature::Signature; pub use state::V01State; -#[cfg(test)] -mod tests; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 5baa996..e2762ba 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -10,8 +10,8 @@ use crate::error::NssaError; #[derive(Debug, PartialEq, Eq)] pub struct Program { - pub(crate) id: ProgramId, - pub(crate) elf: &'static [u8], + id: ProgramId, + elf: &'static [u8], } impl Program { @@ -77,3 +77,130 @@ impl Program { } } } + +#[cfg(test)] +mod tests { + use nssa_core::account::{Account, AccountWithMetadata}; + + use crate::program::Program; + + impl Program { + /// A program that changes the nonce of an account + pub fn nonce_changer_program() -> Self { + use test_program_methods::{NONCE_CHANGER_ELF, NONCE_CHANGER_ID}; + + Program { + id: NONCE_CHANGER_ID, + elf: NONCE_CHANGER_ELF, + } + } + + /// A program that produces more output accounts than the inputs it received + pub fn extra_output_program() -> Self { + use test_program_methods::{EXTRA_OUTPUT_ELF, EXTRA_OUTPUT_ID}; + + Program { + id: EXTRA_OUTPUT_ID, + elf: EXTRA_OUTPUT_ELF, + } + } + + /// A program that produces less output accounts than the inputs it received + pub fn missing_output_program() -> Self { + use test_program_methods::{MISSING_OUTPUT_ELF, MISSING_OUTPUT_ID}; + + Program { + id: MISSING_OUTPUT_ID, + elf: MISSING_OUTPUT_ELF, + } + } + + /// A program that changes the program owner of an account to [0, 1, 2, 3, 4, 5, 6, 7] + pub fn program_owner_changer() -> Self { + use test_program_methods::{PROGRAM_OWNER_CHANGER_ELF, PROGRAM_OWNER_CHANGER_ID}; + + Program { + id: PROGRAM_OWNER_CHANGER_ID, + elf: PROGRAM_OWNER_CHANGER_ELF, + } + } + + /// A program that transfers balance without caring about authorizations + pub fn simple_balance_transfer() -> Self { + use test_program_methods::{SIMPLE_BALANCE_TRANSFER_ELF, SIMPLE_BALANCE_TRANSFER_ID}; + + Program { + id: SIMPLE_BALANCE_TRANSFER_ID, + elf: SIMPLE_BALANCE_TRANSFER_ELF, + } + } + + /// A program that modifies the data of an account + pub fn data_changer() -> Self { + use test_program_methods::{DATA_CHANGER_ELF, DATA_CHANGER_ID}; + + Program { + id: DATA_CHANGER_ID, + elf: DATA_CHANGER_ELF, + } + } + + /// A program that mints balance + pub fn minter() -> Self { + use test_program_methods::{MINTER_ELF, MINTER_ID}; + + Program { + id: MINTER_ID, + elf: MINTER_ELF, + } + } + + /// A program that burns balance + pub fn burner() -> Self { + use test_program_methods::{BURNER_ELF, BURNER_ID}; + + Program { + id: BURNER_ID, + elf: BURNER_ELF, + } + } + } + + #[test] + fn test_program_execution() { + let program = Program::simple_balance_transfer(); + let balance_to_move: u128 = 11223344556677; + let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); + let sender = AccountWithMetadata { + account: Account { + balance: 77665544332211, + ..Account::default() + }, + is_authorized: false, + }; + let recipient = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let expected_sender_post = Account { + balance: 77665544332211 - balance_to_move, + program_owner: program.id(), + ..Account::default() + }; + let expected_recipient_post = Account { + balance: balance_to_move, + // Program claims the account since the pre_state has default prorgam owner + program_owner: program.id(), + ..Account::default() + }; + let [sender_post, recipient_post] = program + .execute(&[sender, recipient], &instruction_data) + .unwrap() + .try_into() + .unwrap(); + + assert_eq!(sender_post, expected_sender_post); + assert_eq!(recipient_post, expected_recipient_post); + } +} diff --git a/nssa/src/public_transaction/encoding.rs b/nssa/src/public_transaction/encoding.rs index d63a95e..7f1ab62 100644 --- a/nssa/src/public_transaction/encoding.rs +++ b/nssa/src/public_transaction/encoding.rs @@ -121,7 +121,7 @@ impl WitnessSet { impl PublicTransaction { pub fn to_bytes(&self) -> Vec { - let mut bytes = self.message.to_bytes(); + let mut bytes = self.message().to_bytes(); bytes.extend_from_slice(&self.witness_set().to_bytes()); bytes } diff --git a/nssa/src/public_transaction/mod.rs b/nssa/src/public_transaction/mod.rs index 9ae24cf..57456f3 100644 --- a/nssa/src/public_transaction/mod.rs +++ b/nssa/src/public_transaction/mod.rs @@ -6,3 +6,4 @@ mod witness_set; pub use message::Message; pub use transaction::PublicTransaction; pub use witness_set::WitnessSet; + diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index dccb4e3..7b5f065 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -15,8 +15,8 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct PublicTransaction { - pub(crate) message: Message, - pub(crate) witness_set: WitnessSet, + message: Message, + witness_set: WitnessSet, } impl PublicTransaction { @@ -113,3 +113,202 @@ impl PublicTransaction { Ok(message.addresses.iter().cloned().zip(post_states).collect()) } } + +#[cfg(test)] +mod tests { + use sha2::{Digest, digest::FixedOutput}; + + use crate::{ + Address, PrivateKey, PublicKey, PublicTransaction, Signature, V01State, + error::NssaError, + program::Program, + public_transaction::{Message, WitnessSet}, + }; + + fn keys_for_tests() -> (PrivateKey, PrivateKey, Address, Address) { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let addr1 = Address::from_public_key(&PublicKey::new_from_private_key(&key1)); + let addr2 = Address::from_public_key(&PublicKey::new_from_private_key(&key2)); + (key1, key2, addr1, addr2) + } + + fn state_for_tests() -> V01State { + let (_, _, addr1, addr2) = keys_for_tests(); + let initial_data = [(*addr1.value(), 10000), (*addr2.value(), 20000)]; + V01State::new_with_genesis_accounts(&initial_data) + } + + fn transaction_for_tests() -> PublicTransaction { + let (key1, key2, addr1, addr2) = keys_for_tests(); + let nonces = vec![0, 0]; + let instruction = 1337; + let message = Message::try_new( + Program::authenticated_transfer_program().id(), + vec![addr1, addr2], + nonces, + instruction, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); + PublicTransaction::new(message, witness_set) + } + + #[test] + fn test_new_constructor() { + let tx = transaction_for_tests(); + let message = tx.message().clone(); + let witness_set = tx.witness_set().clone(); + let tx_from_constructor = PublicTransaction::new(message.clone(), witness_set.clone()); + assert_eq!(tx_from_constructor.message, message); + assert_eq!(tx_from_constructor.witness_set, witness_set); + } + + #[test] + fn test_message_getter() { + let tx = transaction_for_tests(); + assert_eq!(&tx.message, tx.message()); + } + + #[test] + fn test_witness_set_getter() { + let tx = transaction_for_tests(); + assert_eq!(&tx.witness_set, tx.witness_set()); + } + + #[test] + fn test_signer_addresses() { + let tx = transaction_for_tests(); + let expected_signer_addresses = vec![ + Address::new([ + 27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, + 24, 52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143, + ]), + Address::new([ + 77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, + 234, 216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102, + ]), + ]; + let signer_addresses = tx.signer_addresses(); + assert_eq!(signer_addresses, expected_signer_addresses); + } + + #[test] + fn test_public_transaction_encoding_bytes_roundtrip() { + let tx = transaction_for_tests(); + let bytes = tx.to_bytes(); + let tx_from_bytes = PublicTransaction::from_bytes(&bytes).unwrap(); + assert_eq!(tx, tx_from_bytes); + } + + #[test] + fn test_hash_is_sha256_of_transaction_bytes() { + let tx = transaction_for_tests(); + let hash = tx.hash(); + let expected_hash: [u8; 32] = { + let bytes = tx.to_bytes(); + let mut hasher = sha2::Sha256::new(); + hasher.update(&bytes); + hasher.finalize_fixed().into() + }; + assert_eq!(hash, expected_hash); + } + + #[test] + fn test_address_list_cant_have_duplicates() { + let (key1, _, addr1, _) = keys_for_tests(); + let state = state_for_tests(); + let nonces = vec![0, 0]; + let instruction = 1337; + let message = Message::try_new( + Program::authenticated_transfer_program().id(), + vec![addr1.clone(), addr1], + nonces, + instruction, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]); + let tx = PublicTransaction::new(message, witness_set); + let result = tx.validate_and_compute_post_states(&state); + assert!(matches!(result, Err(NssaError::InvalidInput(_)))) + } + + #[test] + fn test_number_of_nonces_must_match_number_of_signatures() { + let (key1, key2, addr1, addr2) = keys_for_tests(); + let state = state_for_tests(); + let nonces = vec![0]; + let instruction = 1337; + let message = Message::try_new( + Program::authenticated_transfer_program().id(), + vec![addr1, addr2], + nonces, + instruction, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); + let tx = PublicTransaction::new(message, witness_set); + let result = tx.validate_and_compute_post_states(&state); + assert!(matches!(result, Err(NssaError::InvalidInput(_)))) + } + + #[test] + fn test_all_signatures_must_be_valid() { + let (key1, key2, addr1, addr2) = keys_for_tests(); + let state = state_for_tests(); + let nonces = vec![0, 0]; + let instruction = 1337; + let message = Message::try_new( + Program::authenticated_transfer_program().id(), + vec![addr1, addr2], + nonces, + instruction, + ) + .unwrap(); + + let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); + witness_set.signatures_and_public_keys[0].0 = Signature { value: [1; 64] }; + let tx = PublicTransaction::new(message, witness_set); + let result = tx.validate_and_compute_post_states(&state); + assert!(matches!(result, Err(NssaError::InvalidInput(_)))) + } + + #[test] + fn test_nonces_must_match_the_state_current_nonces() { + let (key1, key2, addr1, addr2) = keys_for_tests(); + let state = state_for_tests(); + let nonces = vec![0, 1]; + let instruction = 1337; + let message = Message::try_new( + Program::authenticated_transfer_program().id(), + vec![addr1, addr2], + nonces, + instruction, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); + let tx = PublicTransaction::new(message, witness_set); + let result = tx.validate_and_compute_post_states(&state); + assert!(matches!(result, Err(NssaError::InvalidInput(_)))) + } + + #[test] + fn test_program_id_must_belong_to_bulitin_program_ids() { + let (key1, key2, addr1, addr2) = keys_for_tests(); + let state = state_for_tests(); + let nonces = vec![0, 0]; + let instruction = 1337; + let unknown_program_id = [0xdeadbeef; 8]; + let message = + Message::try_new(unknown_program_id, vec![addr1, addr2], nonces, instruction).unwrap(); + + let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); + let tx = PublicTransaction::new(message, witness_set); + let result = tx.validate_and_compute_post_states(&state); + assert!(matches!(result, Err(NssaError::InvalidInput(_)))) + } +} diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index f76de70..4574f56 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -10,7 +10,7 @@ impl WitnessSet { let message_bytes = message.to_bytes(); let signatures_and_public_keys = private_keys .iter() - .map(|&key| (Signature::new(key, &message_bytes), PublicKey::new(key))) + .map(|&key| (Signature::new(key, &message_bytes), PublicKey::new_from_private_key(key))) .collect(); Self { signatures_and_public_keys, diff --git a/nssa/src/tests/bip340_test_vectors.rs b/nssa/src/signature/bip340_test_vectors.rs similarity index 86% rename from nssa/src/tests/bip340_test_vectors.rs rename to nssa/src/signature/bip340_test_vectors.rs index 6b6a3bc..4ee008c 100644 --- a/nssa/src/tests/bip340_test_vectors.rs +++ b/nssa/src/signature/bip340_test_vectors.rs @@ -19,12 +19,12 @@ pub struct TestVector { pub fn test_vectors() -> Vec { vec![ TestVector { - seckey: Some(PrivateKey(hex_to_bytes( + seckey: Some(PrivateKey::try_new(hex_to_bytes( "0000000000000000000000000000000000000000000000000000000000000003", - ))), - pubkey: PublicKey(hex_to_bytes( + )).unwrap()), + pubkey: PublicKey::new(hex_to_bytes( "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", - )), + )).unwrap(), aux_rand: Some(hex_to_bytes::<32>( "0000000000000000000000000000000000000000000000000000000000000000", )), @@ -40,12 +40,12 @@ pub fn test_vectors() -> Vec { verification_result: true, }, TestVector { - seckey: Some(PrivateKey(hex_to_bytes( + seckey: Some(PrivateKey::try_new(hex_to_bytes( "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", - ))), - pubkey: PublicKey(hex_to_bytes( + )).unwrap()), + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: Some(hex_to_bytes::<32>( "0000000000000000000000000000000000000000000000000000000000000001", )), @@ -61,12 +61,12 @@ pub fn test_vectors() -> Vec { verification_result: true, }, TestVector { - seckey: Some(PrivateKey(hex_to_bytes( + seckey: Some(PrivateKey::try_new(hex_to_bytes( "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9", - ))), - pubkey: PublicKey(hex_to_bytes( + )).unwrap()), + pubkey: PublicKey::new(hex_to_bytes( "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", - )), + )).unwrap(), aux_rand: Some(hex_to_bytes::<32>( "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906", )), @@ -82,12 +82,12 @@ pub fn test_vectors() -> Vec { verification_result: true, }, TestVector { - seckey: Some(PrivateKey(hex_to_bytes( + seckey: Some(PrivateKey::try_new(hex_to_bytes( "0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710", - ))), - pubkey: PublicKey(hex_to_bytes( + )).unwrap()), + pubkey: PublicKey::new(hex_to_bytes( "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", - )), + )).unwrap(), aux_rand: Some(hex_to_bytes::<32>( "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", )), @@ -104,9 +104,9 @@ pub fn test_vectors() -> Vec { }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703") @@ -122,9 +122,9 @@ pub fn test_vectors() -> Vec { // Test with invalid public key // TestVector { // seckey: None, - // pubkey: PublicKey(hex_to_bytes( + // pubkey: PublicKey::new(hex_to_bytes( // "EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34", - // )), + // )).unwrap(), // aux_rand: None, // message: Some( // hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89").unwrap(), @@ -138,9 +138,9 @@ pub fn test_vectors() -> Vec { // }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") @@ -155,9 +155,9 @@ pub fn test_vectors() -> Vec { }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") @@ -172,9 +172,9 @@ pub fn test_vectors() -> Vec { }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") @@ -189,9 +189,9 @@ pub fn test_vectors() -> Vec { }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") @@ -206,9 +206,9 @@ pub fn test_vectors() -> Vec { }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") @@ -223,9 +223,9 @@ pub fn test_vectors() -> Vec { }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") @@ -240,9 +240,9 @@ pub fn test_vectors() -> Vec { }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") @@ -257,9 +257,9 @@ pub fn test_vectors() -> Vec { }, TestVector { seckey: None, - pubkey: PublicKey(hex_to_bytes( + pubkey: PublicKey::new(hex_to_bytes( "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - )), + )).unwrap(), aux_rand: None, message: Some( hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") @@ -275,9 +275,9 @@ pub fn test_vectors() -> Vec { // Test with invalid public key // TestVector { // seckey: None, - // pubkey: PublicKey(hex_to_bytes( + // pubkey: PublicKey::new(hex_to_bytes( // "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", - // )), + // )).unwrap(), // aux_rand: None, // message: Some( // hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89").unwrap(), @@ -290,12 +290,12 @@ pub fn test_vectors() -> Vec { // verification_result: false, // }, TestVector { - seckey: Some(PrivateKey(hex_to_bytes( + seckey: Some(PrivateKey::try_new(hex_to_bytes( "0340034003400340034003400340034003400340034003400340034003400340", - ))), - pubkey: PublicKey(hex_to_bytes( + )).unwrap()), + pubkey: PublicKey::new(hex_to_bytes( "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", - )), + )).unwrap(), aux_rand: Some(hex_to_bytes::<32>( "0000000000000000000000000000000000000000000000000000000000000000", )), @@ -308,12 +308,12 @@ pub fn test_vectors() -> Vec { verification_result: true, }, TestVector { - seckey: Some(PrivateKey(hex_to_bytes( + seckey: Some(PrivateKey::try_new(hex_to_bytes( "0340034003400340034003400340034003400340034003400340034003400340", - ))), - pubkey: PublicKey(hex_to_bytes( + )).unwrap()), + pubkey: PublicKey::new(hex_to_bytes( "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", - )), + )).unwrap(), aux_rand: Some(hex_to_bytes::<32>( "0000000000000000000000000000000000000000000000000000000000000000", )), @@ -326,12 +326,12 @@ pub fn test_vectors() -> Vec { verification_result: true, }, TestVector { - seckey: Some(PrivateKey(hex_to_bytes( + seckey: Some(PrivateKey::try_new(hex_to_bytes( "0340034003400340034003400340034003400340034003400340034003400340", - ))), - pubkey: PublicKey(hex_to_bytes( + )).unwrap()), + pubkey: PublicKey::new(hex_to_bytes( "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", - )), + )).unwrap(), aux_rand: Some(hex_to_bytes::<32>( "0000000000000000000000000000000000000000000000000000000000000000", )), @@ -344,12 +344,12 @@ pub fn test_vectors() -> Vec { verification_result: true, }, TestVector { - seckey: Some(PrivateKey(hex_to_bytes( + seckey: Some(PrivateKey::try_new(hex_to_bytes( "0340034003400340034003400340034003400340034003400340034003400340", - ))), - pubkey: PublicKey(hex_to_bytes( + )).unwrap()), + pubkey: PublicKey::new(hex_to_bytes( "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", - )), + )).unwrap(), aux_rand: Some(hex_to_bytes::<32>( "0000000000000000000000000000000000000000000000000000000000000000", )), diff --git a/nssa/src/signature/encoding.rs b/nssa/src/signature/encoding.rs index 7760c00..db5d67e 100644 --- a/nssa/src/signature/encoding.rs +++ b/nssa/src/signature/encoding.rs @@ -6,11 +6,11 @@ impl PublicKey { pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { let mut value = [0u8; 32]; cursor.read_exact(&mut value)?; - Ok(Self(value)) + Ok(Self::new(value)?) } pub(crate) fn to_bytes(&self) -> &[u8] { - &self.0 + self.value() } } diff --git a/nssa/src/signature/mod.rs b/nssa/src/signature/mod.rs index 37b878c..706bc60 100644 --- a/nssa/src/signature/mod.rs +++ b/nssa/src/signature/mod.rs @@ -26,7 +26,7 @@ impl Signature { ) -> Self { let value = { let secp = secp256k1::Secp256k1::new(); - let secret_key = secp256k1::SecretKey::from_byte_array(key.0).unwrap(); + let secret_key = secp256k1::SecretKey::from_byte_array(*key.value()).unwrap(); let keypair = secp256k1::Keypair::from_secret_key(&secp, &secret_key); let signature = secp.sign_schnorr_with_aux_rand(message, &keypair, &aux_random); signature.to_byte_array() @@ -35,9 +35,55 @@ impl Signature { } pub fn is_valid_for(&self, bytes: &[u8], public_key: &PublicKey) -> bool { - let pk = secp256k1::XOnlyPublicKey::from_byte_array(public_key.0).unwrap(); + let pk = secp256k1::XOnlyPublicKey::from_byte_array(*public_key.value()).unwrap(); let secp = secp256k1::Secp256k1::new(); let sig = secp256k1::schnorr::Signature::from_byte_array(self.value); secp.verify_schnorr(&sig, bytes, &pk).is_ok() } } + +#[cfg(test)] +mod bip340_test_vectors; + +#[cfg(test)] +mod tests { + + use crate::{PublicKey, Signature, signature::bip340_test_vectors}; + + #[test] + fn test_signature_generation_from_bip340_test_vectors() { + for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { + let Some(private_key) = test_vector.seckey else { + continue; + }; + let Some(aux_random) = test_vector.aux_rand else { + continue; + }; + let Some(message) = test_vector.message else { + continue; + }; + if !test_vector.verification_result { + continue; + } + let expected_signature = &test_vector.signature; + + let signature = Signature::new_with_aux_random(&private_key, &message, aux_random); + + assert_eq!(&signature, expected_signature, "Failed test vector {i}"); + } + } + + #[test] + fn test_signature_verification_from_bip340_test_vectors() { + for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { + let message = test_vector.message.unwrap_or(vec![]); + let expected_result = test_vector.verification_result; + + let result = test_vector + .signature + .is_valid_for(&message, &test_vector.pubkey); + + assert_eq!(result, expected_result, "Failed test vector {i}"); + } + } +} diff --git a/nssa/src/signature/private_key.rs b/nssa/src/signature/private_key.rs index a58d363..fb5a11c 100644 --- a/nssa/src/signature/private_key.rs +++ b/nssa/src/signature/private_key.rs @@ -6,7 +6,7 @@ use crate::error::NssaError; // 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; 32]); +pub struct PrivateKey([u8; 32]); impl PrivateKey { fn is_valid_key(value: [u8; 32]) -> bool { @@ -20,4 +20,8 @@ impl PrivateKey { Err(NssaError::InvalidPrivateKey) } } + + pub fn value(&self) -> &[u8; 32] { + &self.0 + } } diff --git a/nssa/src/signature/public_key.rs b/nssa/src/signature/public_key.rs index 2f7f4c8..55f6a00 100644 --- a/nssa/src/signature/public_key.rs +++ b/nssa/src/signature/public_key.rs @@ -1,13 +1,13 @@ -use crate::PrivateKey; +use crate::{PrivateKey, error::NssaError}; // TODO: Dummy impl. Replace by actual public key. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct PublicKey(pub(crate) [u8; 32]); +pub struct PublicKey([u8; 32]); impl PublicKey { - pub fn new(key: &PrivateKey) -> Self { + pub fn new_from_private_key(key: &PrivateKey) -> Self { let value = { - let secret_key = secp256k1::SecretKey::from_byte_array(key.0).unwrap(); + let secret_key = secp256k1::SecretKey::from_byte_array(*key.value()).unwrap(); let public_key = secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key); let (x_only, _) = public_key.x_only_public_key(); @@ -15,4 +15,35 @@ impl PublicKey { }; Self(value) } + + pub fn new(value: [u8; 32]) -> Result { + // Check point is valid + let _ = secp256k1::XOnlyPublicKey::from_byte_array(value) + .map_err(|_| NssaError::InvalidPublicKey)?; + Ok(Self(value)) + } + + pub fn value(&self) -> &[u8; 32] { + &self.0 + } +} + +#[cfg(test)] +mod test { + use crate::{PublicKey, signature::bip340_test_vectors}; + + #[test] + fn test_public_key_generation_from_bip340_test_vectors() { + for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { + let Some(private_key) = &test_vector.seckey else { + continue; + }; + let public_key = PublicKey::new_from_private_key(private_key); + let expected_public_key = &test_vector.pubkey; + assert_eq!( + &public_key, expected_public_key, + "Failed test vector at index {i}" + ); + } + } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 343fd6e..9bcd0aa 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -5,8 +5,8 @@ use nssa_core::{account::Account, program::ProgramId}; use std::collections::HashMap; pub struct V01State { - pub(crate) public_state: HashMap, - pub(crate) builtin_programs: HashMap, + public_state: HashMap, + builtin_programs: HashMap, } impl V01State { @@ -79,3 +79,497 @@ impl V01State { self.public_state.insert(address, account); } } + +#[cfg(test)] +mod tests { + + use std::collections::HashMap; + + use crate::{ + Address, PublicKey, PublicTransaction, V01State, error::NssaError, program::Program, + public_transaction, signature::PrivateKey, + }; + use nssa_core::account::Account; + + fn transfer_transaction( + from: Address, + from_key: PrivateKey, + nonce: u128, + to: Address, + balance: u128, + ) -> PublicTransaction { + 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); + PublicTransaction::new(message, witness_set) + } + + #[test] + fn test_new_with_genesis() { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let addr1 = Address::from_public_key(&PublicKey::new_from_private_key(&key1)); + let addr2 = Address::from_public_key(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(*addr1.value(), 100u128), (*addr2.value(), 151u128)]; + let program = Program::authenticated_transfer_program(); + let expected_public_state = { + let mut this = HashMap::new(); + this.insert( + addr1, + Account { + balance: 100, + program_owner: program.id(), + ..Account::default() + }, + ); + this.insert( + addr2, + Account { + balance: 151, + program_owner: program.id(), + ..Account::default() + }, + ); + this + }; + let expected_builtin_programs = { + let mut this = HashMap::new(); + this.insert(program.id(), program); + this + }; + + let state = V01State::new_with_genesis_accounts(&initial_data); + + assert_eq!(state.public_state, expected_public_state); + assert_eq!(state.builtin_programs, expected_builtin_programs); + } + + #[test] + fn test_insert_program() { + let mut state = V01State::new_with_genesis_accounts(&[]); + let program_to_insert = Program::simple_balance_transfer(); + let program_id = program_to_insert.id(); + assert!(!state.builtin_programs.contains_key(&program_id)); + + state.insert_program(program_to_insert); + + assert!(state.builtin_programs.contains_key(&program_id)); + } + + #[test] + fn test_get_account_by_address_non_default_account() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let addr = Address::from_public_key(&PublicKey::new_from_private_key(&key)); + let initial_data = [(*addr.value(), 100u128)]; + let state = V01State::new_with_genesis_accounts(&initial_data); + let expected_account = state.public_state.get(&addr).unwrap(); + + let account = state.get_account_by_address(&addr); + + assert_eq!(&account, expected_account); + } + + #[test] + fn test_get_account_by_address_default_account() { + let addr2 = Address::new([0; 32]); + let state = V01State::new_with_genesis_accounts(&[]); + let expected_account = Account::default(); + + let account = state.get_account_by_address(&addr2); + + assert_eq!(account, expected_account); + } + + #[test] + fn test_builtin_programs_getter() { + let state = V01State::new_with_genesis_accounts(&[]); + + let builtin_programs = state.builtin_programs(); + + assert_eq!(builtin_programs, &state.builtin_programs); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let address = Address::from_public_key(&PublicKey::new_from_private_key(&key)); + let initial_data = [(*address.value(), 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data); + 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(), key, 0, to.clone(), balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_address(&from).balance, 95); + assert_eq!(state.get_account_by_address(&to).balance, 5); + assert_eq!(state.get_account_by_address(&from).nonce, 1); + assert_eq!(state.get_account_by_address(&to).nonce, 0); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let address = Address::from_public_key(&PublicKey::new_from_private_key(&key)); + let initial_data = [(*address.value(), 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data); + 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); + + let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); + assert_eq!(state.get_account_by_address(&from).balance, 100); + assert_eq!(state.get_account_by_address(&to).balance, 0); + assert_eq!(state.get_account_by_address(&from).nonce, 0); + assert_eq!(state.get_account_by_address(&to).nonce, 0); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let address1 = Address::from_public_key(&PublicKey::new_from_private_key(&key1)); + let address2 = Address::from_public_key(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(*address1.value(), 100), (*address2.value(), 200)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data); + 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; + + let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_address(&from).balance, 192); + assert_eq!(state.get_account_by_address(&to).balance, 108); + assert_eq!(state.get_account_by_address(&from).nonce, 1); + assert_eq!(state.get_account_by_address(&to).nonce, 0); + } + + #[test] + fn transition_from_chained_authenticated_transfer_program_invocations() { + let key1 = PrivateKey::try_new([8; 32]).unwrap(); + let address1 = Address::from_public_key(&PublicKey::new_from_private_key(&key1)); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let address2 = Address::from_public_key(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(*address1.value(), 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data); + let address3 = Address::new([3; 32]); + let balance_to_move = 5; + + 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(address2.clone(), key2, 0, address3.clone(), balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + 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); + } + + impl V01State { + /// Include test programs in the builtin programs map + pub fn with_test_programs(mut self) -> Self { + self.insert_program(Program::nonce_changer_program()); + self.insert_program(Program::extra_output_program()); + self.insert_program(Program::missing_output_program()); + self.insert_program(Program::program_owner_changer()); + self.insert_program(Program::simple_balance_transfer()); + self.insert_program(Program::data_changer()); + self.insert_program(Program::minter()); + self.insert_program(Program::burner()); + self + } + + pub fn with_non_default_accounts_but_default_program_owners(mut self) -> Self { + let account_with_default_values_except_balance = Account { + balance: 100, + ..Account::default() + }; + let account_with_default_values_except_nonce = Account { + nonce: 37, + ..Account::default() + }; + let account_with_default_values_except_data = Account { + data: vec![0xca, 0xfe], + ..Account::default() + }; + self.force_insert_account( + Address::new([255; 32]), + account_with_default_values_except_balance, + ); + self.force_insert_account( + Address::new([254; 32]), + account_with_default_values_except_nonce, + ); + self.force_insert_account( + Address::new([253; 32]), + account_with_default_values_except_data, + ); + self + } + + pub fn with_account_owned_by_burner_program(mut self) -> Self { + let account = Account { + program_owner: Program::burner().id(), + balance: 100, + ..Default::default() + }; + self.force_insert_account(Address::new([252; 32]), account); + self + } + } + + #[test] + fn test_program_should_fail_if_modifies_nonces() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + let addresses = vec![Address::new([1; 32])]; + let program_id = Program::nonce_changer_program().id(); + let message = + public_transaction::Message::try_new(program_id, addresses, vec![], ()).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); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_output_accounts_exceed_inputs() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + let addresses = vec![Address::new([1; 32])]; + let program_id = Program::extra_output_program().id(); + let message = + public_transaction::Message::try_new(program_id, addresses, vec![], ()).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); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_with_missing_output_accounts() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + let addresses = vec![Address::new([1; 32]), Address::new([2; 32])]; + let program_id = Program::missing_output_program().id(); + let message = + public_transaction::Message::try_new(program_id, addresses, vec![], ()).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); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() { + let initial_data = [([1; 32], 0)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + let address = Address::new([1; 32]); + let account = state.get_account_by_address(&address); + // Assert the target account only differs from the default account in the program owner field + assert_ne!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_balance() { + let initial_data = []; + let mut state = V01State::new_with_genesis_accounts(&initial_data) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let address = Address::new([255; 32]); + let account = state.get_account_by_address(&address); + // Assert the target account only differs from the default account in balance field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_ne!(account.balance, Account::default().balance); + 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() { + let initial_data = []; + let mut state = V01State::new_with_genesis_accounts(&initial_data) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let address = Address::new([254; 32]); + let account = state.get_account_by_address(&address); + // Assert the target account only differs from the default account in nonce field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data() { + let initial_data = []; + let mut state = V01State::new_with_genesis_accounts(&initial_data) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let address = Address::new([253; 32]); + let account = state.get_account_by_address(&address); + // Assert the target account only differs from the default account in data field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_transfers_balance_from_non_owned_account() { + let initial_data = [([1; 32], 100)]; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + let sender_address = Address::new([1; 32]); + let receiver_address = Address::new([2; 32]); + let balance_to_move: u128 = 1; + let program_id = Program::simple_balance_transfer().id(); + assert_ne!( + state.get_account_by_address(&sender_address).program_owner, + program_id + ); + let message = public_transaction::Message::try_new( + program_id, + vec![sender_address, receiver_address], + vec![], + balance_to_move, + ) + .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); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_modifies_data_of_non_owned_account() { + let initial_data = []; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + let address = Address::new([1; 32]); + let program_id = Program::data_changer().id(); + + // Consider the extreme case where the target account is the default account + assert_eq!(state.get_account_by_address(&address), Account::default()); + assert_ne!( + state.get_account_by_address(&address).program_owner, + program_id + ); + 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); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() { + let initial_data = []; + let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); + 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() { + let initial_data = []; + let mut state = V01State::new_with_genesis_accounts(&initial_data) + .with_test_programs() + .with_account_owned_by_burner_program(); + let program_id = Program::burner().id(); + let address = Address::new([252; 32]); + assert_eq!( + state.get_account_by_address(&address).program_owner, + program_id + ); + let balance_to_burn: u128 = 1; + 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(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } +} diff --git a/nssa/src/tests/mod.rs b/nssa/src/tests/mod.rs deleted file mode 100644 index a2d8eef..0000000 --- a/nssa/src/tests/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod bip340_test_vectors; -mod program_tests; -mod public_transaction_tests; -mod signature_tests; -mod state_tests; -mod valid_execution_tests; diff --git a/nssa/src/tests/program_tests.rs b/nssa/src/tests/program_tests.rs deleted file mode 100644 index 2249c81..0000000 --- a/nssa/src/tests/program_tests.rs +++ /dev/null @@ -1,123 +0,0 @@ -use nssa_core::account::{Account, AccountWithMetadata}; - -use crate::program::Program; - -impl Program { - /// A program that changes the nonce of an account - pub fn nonce_changer_program() -> Self { - use test_program_methods::{NONCE_CHANGER_ELF, NONCE_CHANGER_ID}; - - Program { - id: NONCE_CHANGER_ID, - elf: NONCE_CHANGER_ELF, - } - } - - /// A program that produces more output accounts than the inputs it received - pub fn extra_output_program() -> Self { - use test_program_methods::{EXTRA_OUTPUT_ELF, EXTRA_OUTPUT_ID}; - - Program { - id: EXTRA_OUTPUT_ID, - elf: EXTRA_OUTPUT_ELF, - } - } - - /// A program that produces less output accounts than the inputs it received - pub fn missing_output_program() -> Self { - use test_program_methods::{MISSING_OUTPUT_ELF, MISSING_OUTPUT_ID}; - - Program { - id: MISSING_OUTPUT_ID, - elf: MISSING_OUTPUT_ELF, - } - } - - /// A program that changes the program owner of an account to [0, 1, 2, 3, 4, 5, 6, 7] - pub fn program_owner_changer() -> Self { - use test_program_methods::{PROGRAM_OWNER_CHANGER_ELF, PROGRAM_OWNER_CHANGER_ID}; - - Program { - id: PROGRAM_OWNER_CHANGER_ID, - elf: PROGRAM_OWNER_CHANGER_ELF, - } - } - - /// A program that transfers balance without caring about authorizations - pub fn simple_balance_transfer() -> Self { - use test_program_methods::{SIMPLE_BALANCE_TRANSFER_ELF, SIMPLE_BALANCE_TRANSFER_ID}; - - Program { - id: SIMPLE_BALANCE_TRANSFER_ID, - elf: SIMPLE_BALANCE_TRANSFER_ELF, - } - } - - /// A program that modifies the data of an account - pub fn data_changer() -> Self { - use test_program_methods::{DATA_CHANGER_ELF, DATA_CHANGER_ID}; - - Program { - id: DATA_CHANGER_ID, - elf: DATA_CHANGER_ELF, - } - } - - /// A program that mints balance - pub fn minter() -> Self { - use test_program_methods::{MINTER_ELF, MINTER_ID}; - - Program { - id: MINTER_ID, - elf: MINTER_ELF, - } - } - - /// A program that burns balance - pub fn burner() -> Self { - use test_program_methods::{BURNER_ELF, BURNER_ID}; - - Program { - id: BURNER_ID, - elf: BURNER_ELF, - } - } -} - -#[test] -fn test_program_execution() { - let program = Program::simple_balance_transfer(); - let balance_to_move: u128 = 11223344556677; - let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); - let sender = AccountWithMetadata { - account: Account { - balance: 77665544332211, - ..Account::default() - }, - is_authorized: false, - }; - let recipient = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - }; - - let expected_sender_post = Account { - balance: 77665544332211 - balance_to_move, - program_owner: program.id(), - ..Account::default() - }; - let expected_recipient_post = Account { - balance: balance_to_move, - // Program claims the account since the pre_state has default prorgam owner - program_owner: program.id(), - ..Account::default() - }; - let [sender_post, recipient_post] = program - .execute(&[sender, recipient], &instruction_data) - .unwrap() - .try_into() - .unwrap(); - - assert_eq!(sender_post, expected_sender_post); - assert_eq!(recipient_post, expected_recipient_post); -} diff --git a/nssa/src/tests/public_transaction_tests.rs b/nssa/src/tests/public_transaction_tests.rs deleted file mode 100644 index dd5c1cb..0000000 --- a/nssa/src/tests/public_transaction_tests.rs +++ /dev/null @@ -1,195 +0,0 @@ -use sha2::{Digest, digest::FixedOutput}; - -use crate::{ - Address, PrivateKey, PublicKey, PublicTransaction, Signature, V01State, - error::NssaError, - program::Program, - public_transaction::{Message, WitnessSet}, -}; - -fn keys_for_tests() -> (PrivateKey, PrivateKey, Address, Address) { - let key1 = PrivateKey::try_new([1; 32]).unwrap(); - let key2 = PrivateKey::try_new([2; 32]).unwrap(); - let addr1 = Address::from_public_key(&PublicKey::new(&key1)); - let addr2 = Address::from_public_key(&PublicKey::new(&key2)); - (key1, key2, addr1, addr2) -} - -fn state_for_tests() -> V01State { - let (_, _, addr1, addr2) = keys_for_tests(); - let initial_data = [(*addr1.value(), 10000), (*addr2.value(), 20000)]; - V01State::new_with_genesis_accounts(&initial_data) -} - -fn transaction_for_tests() -> PublicTransaction { - let (key1, key2, addr1, addr2) = keys_for_tests(); - let nonces = vec![0, 0]; - let instruction = 1337; - let message = Message::try_new( - Program::authenticated_transfer_program().id(), - vec![addr1, addr2], - nonces, - instruction, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); - PublicTransaction::new(message, witness_set) -} - -#[test] -fn test_new_constructor() { - let tx = transaction_for_tests(); - let message = tx.message().clone(); - let witness_set = tx.witness_set().clone(); - let tx_from_constructor = PublicTransaction::new(message.clone(), witness_set.clone()); - assert_eq!(tx_from_constructor.message, message); - assert_eq!(tx_from_constructor.witness_set, witness_set); -} - -#[test] -fn test_message_getter() { - let tx = transaction_for_tests(); - assert_eq!(&tx.message, tx.message()); -} - -#[test] -fn test_witness_set_getter() { - let tx = transaction_for_tests(); - assert_eq!(&tx.witness_set, tx.witness_set()); -} - -#[test] -fn test_signer_addresses() { - let tx = transaction_for_tests(); - let expected_signer_addresses = vec![ - Address::new([ - 27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24, - 52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143, - ]), - Address::new([ - 77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, 234, - 216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102, - ]), - ]; - let signer_addresses = tx.signer_addresses(); - assert_eq!(signer_addresses, expected_signer_addresses); -} - -#[test] -fn test_public_transaction_encoding_bytes_roundtrip() { - let tx = transaction_for_tests(); - let bytes = tx.to_bytes(); - let tx_from_bytes = PublicTransaction::from_bytes(&bytes).unwrap(); - assert_eq!(tx, tx_from_bytes); -} - -#[test] -fn test_hash_is_sha256_of_transaction_bytes() { - let tx = transaction_for_tests(); - let hash = tx.hash(); - let expected_hash: [u8; 32] = { - let bytes = tx.to_bytes(); - let mut hasher = sha2::Sha256::new(); - hasher.update(&bytes); - hasher.finalize_fixed().into() - }; - assert_eq!(hash, expected_hash); -} - -#[test] -fn test_address_list_cant_have_duplicates() { - let (key1, _, addr1, _) = keys_for_tests(); - let state = state_for_tests(); - let nonces = vec![0, 0]; - let instruction = 1337; - let message = Message::try_new( - Program::authenticated_transfer_program().id(), - vec![addr1.clone(), addr1], - nonces, - instruction, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]); - let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); - assert!(matches!(result, Err(NssaError::InvalidInput(_)))) -} - -#[test] -fn test_number_of_nonces_must_match_number_of_signatures() { - let (key1, key2, addr1, addr2) = keys_for_tests(); - let state = state_for_tests(); - let nonces = vec![0]; - let instruction = 1337; - let message = Message::try_new( - Program::authenticated_transfer_program().id(), - vec![addr1, addr2], - nonces, - instruction, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); - let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); - assert!(matches!(result, Err(NssaError::InvalidInput(_)))) -} - -#[test] -fn test_all_signatures_must_be_valid() { - let (key1, key2, addr1, addr2) = keys_for_tests(); - let state = state_for_tests(); - let nonces = vec![0, 0]; - let instruction = 1337; - let message = Message::try_new( - Program::authenticated_transfer_program().id(), - vec![addr1, addr2], - nonces, - instruction, - ) - .unwrap(); - - let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); - witness_set.signatures_and_public_keys[0].0 = Signature { value: [1; 64] }; - let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); - assert!(matches!(result, Err(NssaError::InvalidInput(_)))) -} - -#[test] -fn test_nonces_must_match_the_state_current_nonces() { - let (key1, key2, addr1, addr2) = keys_for_tests(); - let state = state_for_tests(); - let nonces = vec![0, 1]; - let instruction = 1337; - let message = Message::try_new( - Program::authenticated_transfer_program().id(), - vec![addr1, addr2], - nonces, - instruction, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); - let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); - assert!(matches!(result, Err(NssaError::InvalidInput(_)))) -} - -#[test] -fn test_program_id_must_belong_to_bulitin_program_ids() { - let (key1, key2, addr1, addr2) = keys_for_tests(); - let state = state_for_tests(); - let nonces = vec![0, 0]; - let instruction = 1337; - let unknown_program_id = [0xdeadbeef; 8]; - let message = - Message::try_new(unknown_program_id, vec![addr1, addr2], nonces, instruction).unwrap(); - - let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); - let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); - assert!(matches!(result, Err(NssaError::InvalidInput(_)))) -} diff --git a/nssa/src/tests/signature_tests.rs b/nssa/src/tests/signature_tests.rs deleted file mode 100644 index fb4d937..0000000 --- a/nssa/src/tests/signature_tests.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{PublicKey, Signature, tests::bip340_test_vectors}; - -#[test] -fn test_signature_generation_from_bip340_test_vectors() { - for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { - let Some(private_key) = test_vector.seckey else { - continue; - }; - let Some(aux_random) = test_vector.aux_rand else { - continue; - }; - let Some(message) = test_vector.message else { - continue; - }; - if !test_vector.verification_result { - continue; - } - let expected_signature = &test_vector.signature; - - let signature = Signature::new_with_aux_random(&private_key, &message, aux_random); - - assert_eq!(&signature, expected_signature, "Failed test vector {i}"); - } -} - -#[test] -fn test_signature_verification_from_bip340_test_vectors() { - for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { - let message = test_vector.message.unwrap_or(vec![]); - let expected_result = test_vector.verification_result; - - let result = test_vector - .signature - .is_valid_for(&message, &test_vector.pubkey); - - assert_eq!(result, expected_result, "Failed test vector {i}"); - } -} - -#[test] -fn test_public_key_generation_from_bip340_test_vectors() { - for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { - let Some(private_key) = &test_vector.seckey else { - continue; - }; - let public_key = PublicKey::new(private_key); - let expected_public_key = &test_vector.pubkey; - assert_eq!( - &public_key, expected_public_key, - "Failed test vector at index {i}" - ); - } -} diff --git a/nssa/src/tests/state_tests.rs b/nssa/src/tests/state_tests.rs deleted file mode 100644 index 7324f8f..0000000 --- a/nssa/src/tests/state_tests.rs +++ /dev/null @@ -1,198 +0,0 @@ -use std::collections::HashMap; - -use crate::{ - Address, PublicKey, PublicTransaction, V01State, error::NssaError, program::Program, - public_transaction, signature::PrivateKey, -}; -use nssa_core::account::Account; - -fn transfer_transaction( - from: Address, - from_key: PrivateKey, - nonce: u128, - to: Address, - balance: u128, -) -> PublicTransaction { - 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); - PublicTransaction::new(message, witness_set) -} - -#[test] -fn test_new_with_genesis() { - let key1 = PrivateKey::try_new([1; 32]).unwrap(); - let key2 = PrivateKey::try_new([2; 32]).unwrap(); - let addr1 = Address::from_public_key(&PublicKey::new(&key1)); - let addr2 = Address::from_public_key(&PublicKey::new(&key2)); - let initial_data = [(*addr1.value(), 100u128), (*addr2.value(), 151u128)]; - let program = Program::authenticated_transfer_program(); - let expected_public_state = { - let mut this = HashMap::new(); - this.insert( - addr1, - Account { - balance: 100, - program_owner: program.id(), - ..Account::default() - }, - ); - this.insert( - addr2, - Account { - balance: 151, - program_owner: program.id(), - ..Account::default() - }, - ); - this - }; - let expected_builtin_programs = { - let mut this = HashMap::new(); - this.insert(program.id(), program); - this - }; - - let state = V01State::new_with_genesis_accounts(&initial_data); - - assert_eq!(state.public_state, expected_public_state); - assert_eq!(state.builtin_programs, expected_builtin_programs); -} - -#[test] -fn test_insert_program() { - let mut state = V01State::new_with_genesis_accounts(&[]); - let program_to_insert = Program::simple_balance_transfer(); - let program_id = program_to_insert.id(); - assert!(!state.builtin_programs.contains_key(&program_id)); - - state.insert_program(program_to_insert); - - assert!(state.builtin_programs.contains_key(&program_id)); -} - -#[test] -fn test_get_account_by_address_non_default_account() { - let key = PrivateKey::try_new([1; 32]).unwrap(); - let addr = Address::from_public_key(&PublicKey::new(&key)); - let initial_data = [(*addr.value(), 100u128)]; - let state = V01State::new_with_genesis_accounts(&initial_data); - let expected_account = state.public_state.get(&addr).unwrap(); - - let account = state.get_account_by_address(&addr); - - assert_eq!(&account, expected_account); -} - -#[test] -fn test_get_account_by_address_default_account() { - let addr2 = Address::new([0; 32]); - let state = V01State::new_with_genesis_accounts(&[]); - let expected_account = Account::default(); - - let account = state.get_account_by_address(&addr2); - - assert_eq!(account, expected_account); -} - -#[test] -fn test_builtin_programs_getter() { - let state = V01State::new_with_genesis_accounts(&[]); - - let builtin_programs = state.builtin_programs(); - - assert_eq!(builtin_programs, &state.builtin_programs); -} - -#[test] -fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { - 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; - 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(), key, 0, to.clone(), balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - - assert_eq!(state.get_account_by_address(&from).balance, 95); - assert_eq!(state.get_account_by_address(&to).balance, 5); - assert_eq!(state.get_account_by_address(&from).nonce, 1); - assert_eq!(state.get_account_by_address(&to).nonce, 0); -} - -#[test] -fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { - 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; - 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); - - let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); - assert_eq!(state.get_account_by_address(&from).balance, 100); - assert_eq!(state.get_account_by_address(&to).balance, 0); - assert_eq!(state.get_account_by_address(&from).nonce, 0); - assert_eq!(state.get_account_by_address(&to).nonce, 0); -} - -#[test] -fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() { - 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 = address2; - let from_key = key2; - let to = address1; - assert_ne!(state.get_account_by_address(&to), Account::default()); - let balance_to_move = 8; - - let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - - assert_eq!(state.get_account_by_address(&from).balance, 192); - assert_eq!(state.get_account_by_address(&to).balance, 108); - assert_eq!(state.get_account_by_address(&from).nonce, 1); - assert_eq!(state.get_account_by_address(&to).nonce, 0); -} - -#[test] -fn transition_from_chained_authenticated_transfer_program_invocations() { - let key1 = PrivateKey::try_new([8; 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 address3 = Address::new([3; 32]); - let balance_to_move = 5; - - 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(address2.clone(), key2, 0, address3.clone(), balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - - 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 deleted file mode 100644 index 0d35b02..0000000 --- a/nssa/src/tests/valid_execution_tests.rs +++ /dev/null @@ -1,289 +0,0 @@ -use nssa_core::account::Account; - -use crate::{ - Address, PublicTransaction, V01State, error::NssaError, program::Program, public_transaction, -}; - -impl V01State { - /// Include test programs in the builtin programs map - pub fn with_test_programs(mut self) -> Self { - self.insert_program(Program::nonce_changer_program()); - self.insert_program(Program::extra_output_program()); - self.insert_program(Program::missing_output_program()); - self.insert_program(Program::program_owner_changer()); - self.insert_program(Program::simple_balance_transfer()); - self.insert_program(Program::data_changer()); - self.insert_program(Program::minter()); - self.insert_program(Program::burner()); - self - } - - pub fn with_non_default_accounts_but_default_program_owners(mut self) -> Self { - let account_with_default_values_except_balance = Account { - balance: 100, - ..Account::default() - }; - let account_with_default_values_except_nonce = Account { - nonce: 37, - ..Account::default() - }; - let account_with_default_values_except_data = Account { - data: vec![0xca, 0xfe], - ..Account::default() - }; - self.force_insert_account( - Address::new([255; 32]), - account_with_default_values_except_balance, - ); - self.force_insert_account( - Address::new([254; 32]), - account_with_default_values_except_nonce, - ); - self.force_insert_account( - Address::new([253; 32]), - account_with_default_values_except_data, - ); - self - } - - pub fn with_account_owned_by_burner_program(mut self) -> Self { - let account = Account { - program_owner: Program::burner().id(), - balance: 100, - ..Default::default() - }; - self.force_insert_account(Address::new([252; 32]), account); - self - } -} - -#[test] -fn test_program_should_fail_if_modifies_nonces() { - let initial_data = [([1; 32], 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); - let addresses = vec![Address::new([1; 32])]; - let program_id = Program::nonce_changer_program().id(); - let message = public_transaction::Message::try_new(program_id, addresses, vec![], ()).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); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_output_accounts_exceed_inputs() { - let initial_data = [([1; 32], 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); - let addresses = vec![Address::new([1; 32])]; - let program_id = Program::extra_output_program().id(); - let message = public_transaction::Message::try_new(program_id, addresses, vec![], ()).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); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_with_missing_output_accounts() { - let initial_data = [([1; 32], 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); - let addresses = vec![Address::new([1; 32]), Address::new([2; 32])]; - let program_id = Program::missing_output_program().id(); - let message = public_transaction::Message::try_new(program_id, addresses, vec![], ()).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); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() { - let initial_data = [([1; 32], 0)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); - let address = Address::new([1; 32]); - let account = state.get_account_by_address(&address); - // Assert the target account only differs from the default account in the program owner field - assert_ne!(account.program_owner, Account::default().program_owner); - assert_eq!(account.balance, Account::default().balance); - 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_balance() { - let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data) - .with_test_programs() - .with_non_default_accounts_but_default_program_owners(); - let address = Address::new([255; 32]); - let account = state.get_account_by_address(&address); - // Assert the target account only differs from the default account in balance field - assert_eq!(account.program_owner, Account::default().program_owner); - assert_ne!(account.balance, Account::default().balance); - 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() { - let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data) - .with_test_programs() - .with_non_default_accounts_but_default_program_owners(); - let address = Address::new([254; 32]); - let account = state.get_account_by_address(&address); - // Assert the target account only differs from the default account in nonce field - assert_eq!(account.program_owner, Account::default().program_owner); - assert_eq!(account.balance, Account::default().balance); - 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data() { - let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data) - .with_test_programs() - .with_non_default_accounts_but_default_program_owners(); - let address = Address::new([253; 32]); - let account = state.get_account_by_address(&address); - // Assert the target account only differs from the default account in data field - assert_eq!(account.program_owner, Account::default().program_owner); - assert_eq!(account.balance, Account::default().balance); - 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_transfers_balance_from_non_owned_account() { - let initial_data = [([1; 32], 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); - let sender_address = Address::new([1; 32]); - let receiver_address = Address::new([2; 32]); - let balance_to_move: u128 = 1; - let program_id = Program::simple_balance_transfer().id(); - assert_ne!( - state.get_account_by_address(&sender_address).program_owner, - program_id - ); - let message = public_transaction::Message::try_new( - program_id, - vec![sender_address, receiver_address], - vec![], - balance_to_move, - ) - .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); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_modifies_data_of_non_owned_account() { - let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); - let address = Address::new([1; 32]); - let program_id = Program::data_changer().id(); - - // Consider the extreme case where the target account is the default account - assert_eq!(state.get_account_by_address(&address), Account::default()); - assert_ne!( - state.get_account_by_address(&address).program_owner, - program_id - ); - 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); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() { - let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs(); - 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 witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -} - -#[test] -fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() { - let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data) - .with_test_programs() - .with_account_owned_by_burner_program(); - let program_id = Program::burner().id(); - let address = Address::new([252; 32]); - assert_eq!( - state.get_account_by_address(&address).program_owner, - program_id - ); - let balance_to_burn: u128 = 1; - 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(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); -}