diff --git a/integration_tests/data_changer.bin b/integration_tests/data_changer.bin new file mode 100644 index 0000000..eb28a62 Binary files /dev/null and b/integration_tests/data_changer.bin differ diff --git a/integration_tests/src/data_changer.bin b/integration_tests/src/data_changer.bin deleted file mode 100644 index 3d062c3..0000000 Binary files a/integration_tests/src/data_changer.bin and /dev/null differ diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 31cd177..401238f 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -42,7 +42,7 @@ pub const ACC_RECEIVER_PRIVATE: &str = "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9e pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; -pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &[u8] = include_bytes!("data_changer.bin"); +pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &str = "data_changer.bin"; fn make_public_account_input_from_str(account_id: &str) -> String { format!("Public/{account_id}") diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 7ee3ef4..8012e17 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -2,6 +2,7 @@ use std::{ collections::HashMap, path::PathBuf, pin::Pin, + str::FromStr, time::{Duration, Instant}, }; @@ -10,7 +11,7 @@ use anyhow::Result; use common::{PINATA_BASE58, sequencer_client::SequencerClient}; use key_protocol::key_management::key_tree::chain_index::ChainIndex; use log::info; -use nssa::{AccountId, ProgramDeploymentTransaction, program::Program}; +use nssa::{AccountId, program::Program}; use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; use sequencer_runner::startup_sequencer; use tempfile::TempDir; @@ -86,9 +87,7 @@ pub fn prepare_function_map() -> HashMap { #[nssa_integration_test] pub async fn test_success_move_to_another_account() { info!("########## test_success_move_to_another_account ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: ChainIndex::root(), - })); + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); let wallet_config = fetch_config().await.unwrap(); @@ -292,9 +291,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: ChainIndex::root(), - }, + NewSubcommand::Public { cci: None }, ))) .await .unwrap() @@ -305,9 +302,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: ChainIndex::root(), - }, + NewSubcommand::Public { cci: None }, ))) .await .unwrap() @@ -318,9 +313,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: ChainIndex::root(), - }, + NewSubcommand::Public { cci: None }, ))) .await .unwrap() @@ -444,20 +437,18 @@ pub fn prepare_function_map() -> HashMap { } /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. All accounts are owned except - /// definition. + /// test executes a private token transfer to a new account. All accounts are private owned + /// except definition which is public. #[nssa_integration_test] - pub async fn test_success_token_program_private_owned() { - info!("########## test_success_token_program_private_owned ##########"); + pub async fn test_success_token_program_private_owned_supply() { + info!("########## test_success_token_program_private_owned_supply ##########"); let wallet_config = fetch_config().await.unwrap(); // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: ChainIndex::root(), - }, + NewSubcommand::Public { cci: None }, ))) .await .unwrap() @@ -468,9 +459,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: ChainIndex::root(), - }, + NewSubcommand::Private { cci: None }, ))) .await .unwrap() @@ -481,9 +470,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: ChainIndex::root(), - }, + NewSubcommand::Private { cci: None }, ))) .await .unwrap() @@ -605,19 +592,104 @@ pub fn prepare_function_map() -> HashMap { assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); } - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. + /// This test creates a new private token using the token program. All accounts are private + /// owned except supply which is public. #[nssa_integration_test] - pub async fn test_success_token_program_private_claiming_path() { - info!("########## test_success_token_program_private_claiming_path ##########"); + pub async fn test_success_token_program_private_owned_definition() { + info!("########## test_success_token_program_private_owned_definition ##########"); let wallet_config = fetch_config().await.unwrap(); - // Create new account for the token definition (public) + // Create new account for the token definition (private) let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Private { + cci: Some(ChainIndex::root()), + }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for the token supply holder (public) + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { - cci: ChainIndex::root(), + cci: Some(ChainIndex::root()), + }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: make_private_account_input_from_str( + &definition_account_id.to_string(), + ), + supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(Command::Token(subcommand)) + .await + .unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + let wallet_config = fetch_config().await.unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&definition_account_id) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + // Check the status of the token definition account is the expected after the execution + let supply_acc = seq_client + .get_account(supply_account_id.to_string()) + .await + .unwrap() + .account; + + assert_eq!(supply_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + assert_eq!( + supply_acc.data.as_ref(), + &[ + 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, + 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } + + /// This test creates a new private token using the token program. All accounts are private + /// owned. + #[nssa_integration_test] + pub async fn test_success_token_program_private_owned_definition_and_supply() { + info!( + "########## test_success_token_program_private_owned_definition_and_supply ##########" + ); + let wallet_config = fetch_config().await.unwrap(); + + // Create new account for the token definition (private) + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Private { + cci: Some(ChainIndex::root()), }, ))) .await @@ -630,7 +702,7 @@ pub fn prepare_function_map() -> HashMap { account_id: supply_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Private { - cci: ChainIndex::root(), + cci: Some(ChainIndex::root()), }, ))) .await @@ -638,13 +710,105 @@ pub fn prepare_function_map() -> HashMap { else { panic!("invalid subcommand return value"); }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: make_private_account_input_from_str( + &definition_account_id.to_string(), + ), + supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(Command::Token(subcommand)) + .await + .unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + let wallet_config = fetch_config().await.unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&definition_account_id) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + let new_commitment2 = wallet_storage + .get_private_account_commitment(&supply_account_id) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); + + let definition_acc = wallet_storage + .get_account_private(&definition_account_id) + .unwrap(); + let supply_acc = wallet_storage + .get_account_private(&supply_account_id) + .unwrap(); + + assert_eq!(definition_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + assert_eq!(supply_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + assert_eq!( + supply_acc.data.as_ref(), + &[ + 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, + 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } + + /// This test creates a new private token using the token program. After creating the token, the + /// test executes a private token transfer to a new account. + #[nssa_integration_test] + pub async fn test_success_token_program_private_claiming_path() { + info!("########## test_success_token_program_private_claiming_path ##########"); + let wallet_config = fetch_config().await.unwrap(); + + // Create new account for the token definition (public) + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for the token supply holder (private) + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Private { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: ChainIndex::root(), - }, + NewSubcommand::Private { cci: None }, ))) .await .unwrap() @@ -758,9 +922,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: ChainIndex::root(), - }, + NewSubcommand::Public { cci: None }, ))) .await .unwrap() @@ -771,9 +933,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: ChainIndex::root(), - }, + NewSubcommand::Public { cci: None }, ))) .await .unwrap() @@ -784,9 +944,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: ChainIndex::root(), - }, + NewSubcommand::Private { cci: None }, ))) .await .unwrap() @@ -900,9 +1058,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: ChainIndex::root(), - }, + NewSubcommand::Public { cci: None }, ))) .await .unwrap() @@ -913,9 +1069,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: ChainIndex::root(), - }, + NewSubcommand::Private { cci: None }, ))) .await .unwrap() @@ -926,9 +1080,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: ChainIndex::root(), - }, + NewSubcommand::Public { cci: None }, ))) .await .unwrap() @@ -1129,9 +1281,8 @@ pub fn prepare_function_map() -> HashMap { ); let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: ChainIndex::root(), - })); + let command = + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); let SubcommandReturnValue::RegisterAccount { @@ -1444,15 +1595,18 @@ pub fn prepare_function_map() -> HashMap { #[nssa_integration_test] pub async fn test_program_deployment() { info!("########## test program deployment ##########"); - let bytecode = NSSA_PROGRAM_FOR_TEST_DATA_CHANGER.to_vec(); - let message = nssa::program_deployment_transaction::Message::new(bytecode.clone()); - let transaction = ProgramDeploymentTransaction::new(message); + + let binary_filepath: PathBuf = NSSA_PROGRAM_FOR_TEST_DATA_CHANGER.parse().unwrap(); + + let command = Command::DeployProgram { + binary_filepath: binary_filepath.clone(), + }; + + wallet::cli::execute_subcommand(command).await.unwrap(); let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let _response = seq_client.send_tx_program(transaction).await.unwrap(); - info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -1460,6 +1614,8 @@ pub fn prepare_function_map() -> HashMap { // We pass an uninitialized account and we expect after execution to be owned by the data // changer program (NSSA account claiming mechanism) with data equal to [0] (due to program // logic) + // + let bytecode = std::fs::read(binary_filepath).unwrap(); let data_changer = Program::new(bytecode).unwrap(); let account_id: AccountId = "11".repeat(16).parse().unwrap(); let message = nssa::public_transaction::Message::try_new( @@ -1492,9 +1648,7 @@ pub fn prepare_function_map() -> HashMap { #[nssa_integration_test] pub async fn test_authenticated_transfer_initialize_function() { info!("########## test initialize account for authenticated transfer ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: ChainIndex::root(), - })); + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); let SubcommandReturnValue::RegisterAccount { account_id } = wallet::cli::execute_subcommand(command).await.unwrap() else { @@ -1592,9 +1746,7 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { account_id: winner_account_id, } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: ChainIndex::root(), - }, + NewSubcommand::Private { cci: None }, ))) .await .unwrap() @@ -1672,6 +1824,217 @@ pub fn prepare_function_map() -> HashMap { info!("Success!"); } + #[nssa_integration_test] + pub async fn test_keys_restoration() { + info!("########## test_keys_restoration ##########"); + let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::root()), + })); + + let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id1, + } = sub_ret + else { + panic!("FAILED TO REGISTER ACCOUNT"); + }; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::from_str("/0").unwrap()), + })); + + let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id2, + } = sub_ret + else { + panic!("FAILED TO REGISTER ACCOUNT"); + }; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: make_private_account_input_from_str(&from.to_string()), + to: Some(make_private_account_input_from_str( + &to_account_id1.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(command).await.unwrap(); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: make_private_account_input_from_str(&from.to_string()), + to: Some(make_private_account_input_from_str( + &to_account_id2.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 101, + }); + + wallet::cli::execute_subcommand(command).await.unwrap(); + + let from: AccountId = ACC_SENDER.parse().unwrap(); + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::root()), + })); + + let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id3, + } = sub_ret + else { + panic!("FAILED TO REGISTER ACCOUNT"); + }; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::from_str("/0").unwrap()), + })); + + let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id4, + } = sub_ret + else { + panic!("FAILED TO REGISTER ACCOUNT"); + }; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: make_public_account_input_from_str(&from.to_string()), + to: Some(make_public_account_input_from_str( + &to_account_id3.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 102, + }); + + wallet::cli::execute_subcommand(command).await.unwrap(); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: make_public_account_input_from_str(&from.to_string()), + to: Some(make_public_account_input_from_str( + &to_account_id4.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 103, + }); + + wallet::cli::execute_subcommand(command).await.unwrap(); + + info!("########## PREPARATION END ##########"); + + wallet::cli::execute_keys_restoration("test_pass".to_string(), 10) + .await + .unwrap(); + + let wallet_config = fetch_config().await.unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) + .await + .unwrap(); + + let acc1 = wallet_storage + .storage + .user_data + .private_key_tree + .get_node(to_account_id1) + .expect("Acc 1 should be restored"); + + let acc2 = wallet_storage + .storage + .user_data + .private_key_tree + .get_node(to_account_id2) + .expect("Acc 2 should be restored"); + + let _ = wallet_storage + .storage + .user_data + .public_key_tree + .get_node(to_account_id3) + .expect("Acc 3 should be restored"); + + let _ = wallet_storage + .storage + .user_data + .public_key_tree + .get_node(to_account_id4) + .expect("Acc 4 should be restored"); + + assert_eq!( + acc1.value.1.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!( + acc2.value.1.program_owner, + Program::authenticated_transfer_program().id() + ); + + assert_eq!(acc1.value.1.balance, 100); + assert_eq!(acc2.value.1.balance, 101); + + info!("########## TREE CHECKS END ##########"); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: make_private_account_input_from_str(&to_account_id1.to_string()), + to: Some(make_private_account_input_from_str( + &to_account_id2.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 10, + }); + + wallet::cli::execute_subcommand(command).await.unwrap(); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: make_public_account_input_from_str(&to_account_id3.to_string()), + to: Some(make_public_account_input_from_str( + &to_account_id4.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 11, + }); + + wallet::cli::execute_subcommand(command).await.unwrap(); + + let wallet_config = fetch_config().await.unwrap(); + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) + .await + .unwrap(); + + let comm1 = wallet_storage + .get_private_account_commitment(&to_account_id1) + .expect("Acc 1 commitment should exist"); + let comm2 = wallet_storage + .get_private_account_commitment(&to_account_id2) + .expect("Acc 2 commitment should exist"); + + assert!(verify_commitment_is_in_state(comm1, &seq_client).await); + assert!(verify_commitment_is_in_state(comm2, &seq_client).await); + + let acc3 = seq_client + .get_account_balance(to_account_id3.to_string()) + .await + .expect("Acc 3 must be present in public state"); + let acc4 = seq_client + .get_account_balance(to_account_id4.to_string()) + .await + .expect("Acc 4 must be present in public state"); + + assert_eq!(acc3.balance, 91); + assert_eq!(acc4.balance, 114); + + info!("Success!"); + } + println!("{function_map:#?}"); function_map diff --git a/integration_tests/src/tps_test_utils.rs b/integration_tests/src/tps_test_utils.rs index 29462f6..6f597e2 100644 --- a/integration_tests/src/tps_test_utils.rs +++ b/integration_tests/src/tps_test_utils.rs @@ -168,7 +168,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { (recipient_npk.clone(), recipient_ss), ], &[(sender_nsk, proof)], - &program, + &program.into(), ) .unwrap(); let message = pptx::message::Message::try_from_circuit_output( diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index a562515..103a1de 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -16,6 +16,7 @@ bip39.workspace = true hmac-sha512.workspace = true thiserror.workspace = true nssa-core = { path = "../nssa/core", features = ["host"] } +itertools.workspace = true [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/key_tree/chain_index.rs b/key_protocol/src/key_management/key_tree/chain_index.rs index e46fc0f..6dbaf9a 100644 --- a/key_protocol/src/key_management/key_tree/chain_index.rs +++ b/key_protocol/src/key_management/key_tree/chain_index.rs @@ -1,8 +1,9 @@ use std::{fmt::Display, str::FromStr}; +use itertools::Itertools; use serde::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize, Hash)] pub struct ChainIndex(Vec); #[derive(thiserror::Error, Debug)] @@ -77,12 +78,82 @@ impl ChainIndex { ChainIndex(chain) } + pub fn previous_in_line(&self) -> Option { + let mut chain = self.0.clone(); + if let Some(last_p) = chain.last_mut() { + *last_p = last_p.checked_sub(1)?; + } + + Some(ChainIndex(chain)) + } + + pub fn parent(&self) -> Option { + if self.0.is_empty() { + None + } else { + Some(ChainIndex(self.0[..(self.0.len() - 1)].to_vec())) + } + } + pub fn nth_child(&self, child_id: u32) -> ChainIndex { let mut chain = self.0.clone(); chain.push(child_id); ChainIndex(chain) } + + pub fn depth(&self) -> u32 { + self.0.iter().map(|cci| cci + 1).sum() + } + + fn collapse_back(&self) -> Option { + let mut res = self.parent()?; + + let last_mut = res.0.last_mut()?; + *last_mut += *(self.0.last()?) + 1; + + Some(res) + } + + fn shuffle_iter(&self) -> impl Iterator { + self.0 + .iter() + .permutations(self.0.len()) + .unique() + .map(|item| ChainIndex(item.into_iter().cloned().collect())) + } + + pub fn chain_ids_at_depth(depth: usize) -> impl Iterator { + let mut stack = vec![ChainIndex(vec![0; depth])]; + let mut cumulative_stack = vec![ChainIndex(vec![0; depth])]; + + while let Some(id) = stack.pop() { + if let Some(collapsed_id) = id.collapse_back() { + for id in collapsed_id.shuffle_iter() { + stack.push(id.clone()); + cumulative_stack.push(id); + } + } + } + + cumulative_stack.into_iter().unique() + } + + pub fn chain_ids_at_depth_rev(depth: usize) -> impl Iterator { + let mut stack = vec![ChainIndex(vec![0; depth])]; + let mut cumulative_stack = vec![ChainIndex(vec![0; depth])]; + + while let Some(id) = stack.pop() { + if let Some(collapsed_id) = id.collapse_back() { + for id in collapsed_id.shuffle_iter() { + stack.push(id.clone()); + cumulative_stack.push(id); + } + } + } + + cumulative_stack.into_iter().rev().unique() + } } #[cfg(test)] @@ -145,4 +216,83 @@ mod tests { assert_eq!(string_index, "/5/7/8".to_string()); } + + #[test] + fn test_prev_in_line() { + let chain_id = ChainIndex(vec![1, 7, 3]); + + let prev_chain_id = chain_id.previous_in_line().unwrap(); + + assert_eq!(prev_chain_id, ChainIndex(vec![1, 7, 2])) + } + + #[test] + fn test_prev_in_line_no_prev() { + let chain_id = ChainIndex(vec![1, 7, 0]); + + let prev_chain_id = chain_id.previous_in_line(); + + assert_eq!(prev_chain_id, None) + } + + #[test] + fn test_parent() { + let chain_id = ChainIndex(vec![1, 7, 3]); + + let parent_chain_id = chain_id.parent().unwrap(); + + assert_eq!(parent_chain_id, ChainIndex(vec![1, 7])) + } + + #[test] + fn test_parent_no_parent() { + let chain_id = ChainIndex(vec![]); + + let parent_chain_id = chain_id.parent(); + + assert_eq!(parent_chain_id, None) + } + + #[test] + fn test_parent_root() { + let chain_id = ChainIndex(vec![1]); + + let parent_chain_id = chain_id.parent().unwrap(); + + assert_eq!(parent_chain_id, ChainIndex::root()) + } + + #[test] + fn test_collapse_back() { + let chain_id = ChainIndex(vec![1, 1]); + + let collapsed = chain_id.collapse_back().unwrap(); + + assert_eq!(collapsed, ChainIndex(vec![3])) + } + + #[test] + fn test_collapse_back_one() { + let chain_id = ChainIndex(vec![1]); + + let collapsed = chain_id.collapse_back(); + + assert_eq!(collapsed, None) + } + + #[test] + fn test_collapse_back_root() { + let chain_id = ChainIndex(vec![]); + + let collapsed = chain_id.collapse_back(); + + assert_eq!(collapsed, None) + } + + #[test] + fn test_shuffle() { + for id in ChainIndex::chain_ids_at_depth(5) { + println!("{id}"); + } + } } diff --git a/key_protocol/src/key_management/key_tree/mod.rs b/key_protocol/src/key_management/key_tree/mod.rs index 1319a5a..389580b 100644 --- a/key_protocol/src/key_management/key_tree/mod.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -1,5 +1,10 @@ -use std::collections::{BTreeMap, HashMap}; +use std::{ + collections::{BTreeMap, HashMap}, + sync::Arc, +}; +use anyhow::Result; +use common::sequencer_client::SequencerClient; use serde::{Deserialize, Serialize}; use crate::key_management::{ @@ -15,6 +20,8 @@ pub mod keys_private; pub mod keys_public; pub mod traits; +pub const DEPTH_SOFT_CAP: u32 = 20; + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct KeyTree { pub key_map: BTreeMap, @@ -96,21 +103,53 @@ impl KeyTree { } } - pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option { - let father_keys = self.key_map.get(&parent_cci)?; + pub fn generate_new_node( + &mut self, + parent_cci: &ChainIndex, + ) -> Option<(nssa::AccountId, ChainIndex)> { + let parent_keys = self.key_map.get(parent_cci)?; let next_child_id = self - .find_next_last_child_of_id(&parent_cci) + .find_next_last_child_of_id(parent_cci) .expect("Can be None only if parent is not present"); let next_cci = parent_cci.nth_child(next_child_id); - let child_keys = father_keys.nth_child(next_child_id); - + let child_keys = parent_keys.nth_child(next_child_id); let account_id = child_keys.account_id(); self.key_map.insert(next_cci.clone(), child_keys); - self.account_id_map.insert(account_id, next_cci); + self.account_id_map.insert(account_id, next_cci.clone()); - Some(account_id) + Some((account_id, next_cci)) + } + + fn find_next_slot_layered(&self) -> ChainIndex { + let mut depth = 1; + + 'outer: loop { + for chain_id in ChainIndex::chain_ids_at_depth_rev(depth) { + if !self.key_map.contains_key(&chain_id) { + break 'outer chain_id; + } + } + depth += 1; + } + } + + pub fn fill_node(&mut self, chain_index: &ChainIndex) -> Option<(nssa::AccountId, ChainIndex)> { + let parent_keys = self.key_map.get(&chain_index.parent()?)?; + let child_id = *chain_index.chain().last()?; + + let child_keys = parent_keys.nth_child(child_id); + let account_id = child_keys.account_id(); + + self.key_map.insert(chain_index.clone(), child_keys); + self.account_id_map.insert(account_id, chain_index.clone()); + + Some((account_id, chain_index.clone())) + } + + pub fn generate_new_node_layered(&mut self) -> Option<(nssa::AccountId, ChainIndex)> { + self.fill_node(&self.find_next_slot_layered()) } pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> { @@ -129,11 +168,164 @@ impl KeyTree { self.account_id_map.insert(account_id, chain_index.clone()); self.key_map.insert(chain_index, node); } + + pub fn remove(&mut self, addr: nssa::AccountId) -> Option { + let chain_index = self.account_id_map.remove(&addr).unwrap(); + self.key_map.remove(&chain_index) + } + + /// Populates tree with children. + /// + /// For given `depth` adds children to a tree such that their `ChainIndex::depth(&self) < + /// depth`. + /// + /// Tree must be empty before start + pub fn generate_tree_for_depth(&mut self, depth: u32) { + let mut id_stack = vec![ChainIndex::root()]; + + while let Some(curr_id) = id_stack.pop() { + let mut next_id = curr_id.nth_child(0); + + while (next_id.depth()) < depth { + self.generate_new_node(&curr_id); + id_stack.push(next_id.clone()); + next_id = next_id.next_in_line(); + } + } + } +} + +impl KeyTree { + /// Cleanup of all non-initialized accounts in a private tree + /// + /// For given `depth` checks children to a tree such that their `ChainIndex::depth(&self) < + /// depth`. + /// + /// If account is default, removes them. + /// + /// Chain must be parsed for accounts beforehand + /// + /// Fast, leaves gaps between accounts + pub fn cleanup_tree_remove_uninit_for_depth(&mut self, depth: u32) { + let mut id_stack = vec![ChainIndex::root()]; + + while let Some(curr_id) = id_stack.pop() { + if let Some(node) = self.key_map.get(&curr_id) + && node.value.1 == nssa::Account::default() + && curr_id != ChainIndex::root() + { + let addr = node.account_id(); + self.remove(addr); + } + + let mut next_id = curr_id.nth_child(0); + + while (next_id.depth()) < depth { + id_stack.push(next_id.clone()); + next_id = next_id.next_in_line(); + } + } + } + + /// Cleanup of non-initialized accounts in a private tree + /// + /// If account is default, removes them, stops at first non-default account. + /// + /// Walks through tree in lairs of same depth using `ChainIndex::chain_ids_at_depth()` + /// + /// Chain must be parsed for accounts beforehand + /// + /// Slow, maintains tree consistency. + pub fn cleanup_tree_remove_uninit_layered(&mut self, depth: u32) { + 'outer: for i in (1..(depth as usize)).rev() { + println!("Cleanup of tree at depth {i}"); + for id in ChainIndex::chain_ids_at_depth(i) { + if let Some(node) = self.key_map.get(&id) { + if node.value.1 == nssa::Account::default() { + let addr = node.account_id(); + self.remove(addr); + } else { + break 'outer; + } + } + } + } + } +} + +impl KeyTree { + /// Cleanup of all non-initialized accounts in a public tree + /// + /// For given `depth` checks children to a tree such that their `ChainIndex::depth(&self) < + /// depth`. + /// + /// If account is default, removes them. + /// + /// Fast, leaves gaps between accounts + pub async fn cleanup_tree_remove_ininit_for_depth( + &mut self, + depth: u32, + client: Arc, + ) -> Result<()> { + let mut id_stack = vec![ChainIndex::root()]; + + while let Some(curr_id) = id_stack.pop() { + if let Some(node) = self.key_map.get(&curr_id) { + let address = node.account_id(); + let node_acc = client.get_account(address.to_string()).await?.account; + + if node_acc == nssa::Account::default() && curr_id != ChainIndex::root() { + self.remove(address); + } + } + + let mut next_id = curr_id.nth_child(0); + + while (next_id.depth()) < depth { + id_stack.push(next_id.clone()); + next_id = next_id.next_in_line(); + } + } + + Ok(()) + } + + /// Cleanup of non-initialized accounts in a public tree + /// + /// If account is default, removes them, stops at first non-default account. + /// + /// Walks through tree in lairs of same depth using `ChainIndex::chain_ids_at_depth()` + /// + /// Slow, maintains tree consistency. + pub async fn cleanup_tree_remove_uninit_layered( + &mut self, + depth: u32, + client: Arc, + ) -> Result<()> { + 'outer: for i in (1..(depth as usize)).rev() { + println!("Cleanup of tree at depth {i}"); + for id in ChainIndex::chain_ids_at_depth(i) { + if let Some(node) = self.key_map.get(&id) { + let address = node.account_id(); + let node_acc = client.get_account(address.to_string()).await?.account; + + if node_acc == nssa::Account::default() { + let addr = node.account_id(); + self.remove(addr); + } else { + break 'outer; + } + } + } + } + + Ok(()) + } } #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{collections::HashSet, str::FromStr}; use nssa::AccountId; @@ -162,7 +354,7 @@ mod tests { fn test_small_key_tree() { let seed_holder = seed_holder_for_tests(); - let mut tree = KeyTreePublic::new(&seed_holder); + let mut tree = KeyTreePrivate::new(&seed_holder); let next_last_child_for_parent_id = tree .find_next_last_child_of_id(&ChainIndex::root()) @@ -170,7 +362,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 0); - tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); assert!( tree.key_map @@ -183,12 +375,12 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 1); - tree.generate_new_node(ChainIndex::root()).unwrap(); - tree.generate_new_node(ChainIndex::root()).unwrap(); - tree.generate_new_node(ChainIndex::root()).unwrap(); - tree.generate_new_node(ChainIndex::root()).unwrap(); - tree.generate_new_node(ChainIndex::root()).unwrap(); - tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); let next_last_child_for_parent_id = tree .find_next_last_child_of_id(&ChainIndex::root()) @@ -201,7 +393,7 @@ mod tests { fn test_key_tree_can_not_make_child_keys() { let seed_holder = seed_holder_for_tests(); - let mut tree = KeyTreePublic::new(&seed_holder); + let mut tree = KeyTreePrivate::new(&seed_holder); let next_last_child_for_parent_id = tree .find_next_last_child_of_id(&ChainIndex::root()) @@ -209,7 +401,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 0); - tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); assert!( tree.key_map @@ -222,7 +414,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 1); - let key_opt = tree.generate_new_node(ChainIndex::from_str("/3").unwrap()); + let key_opt = tree.generate_new_node(&ChainIndex::from_str("/3").unwrap()); assert_eq!(key_opt, None); } @@ -239,7 +431,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 0); - tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); assert!( tree.key_map @@ -252,7 +444,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 1); - tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(&ChainIndex::root()).unwrap(); assert!( tree.key_map @@ -265,7 +457,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 2); - tree.generate_new_node(ChainIndex::from_str("/0").unwrap()) + tree.generate_new_node(&ChainIndex::from_str("/0").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree @@ -279,7 +471,7 @@ mod tests { .contains_key(&ChainIndex::from_str("/0/0").unwrap()) ); - tree.generate_new_node(ChainIndex::from_str("/0").unwrap()) + tree.generate_new_node(&ChainIndex::from_str("/0").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree @@ -293,7 +485,7 @@ mod tests { .contains_key(&ChainIndex::from_str("/0/1").unwrap()) ); - tree.generate_new_node(ChainIndex::from_str("/0").unwrap()) + tree.generate_new_node(&ChainIndex::from_str("/0").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree @@ -307,7 +499,7 @@ mod tests { .contains_key(&ChainIndex::from_str("/0/2").unwrap()) ); - tree.generate_new_node(ChainIndex::from_str("/0/1").unwrap()) + tree.generate_new_node(&ChainIndex::from_str("/0/1").unwrap()) .unwrap(); assert!( @@ -321,4 +513,94 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 1); } + + #[test] + fn test_tree_balancing_automatic() { + let seed_holder = seed_holder_for_tests(); + + let mut tree = KeyTreePublic::new(&seed_holder); + + for _ in 0..100 { + tree.generate_new_node_layered().unwrap(); + } + + let next_slot = tree.find_next_slot_layered(); + + assert_eq!(next_slot, ChainIndex::from_str("/0/0/2/1").unwrap()); + } + + #[test] + fn test_cleanup() { + let seed_holder = seed_holder_for_tests(); + + let mut tree = KeyTreePrivate::new(&seed_holder); + tree.generate_tree_for_depth(10); + + let acc = tree + .key_map + .get_mut(&ChainIndex::from_str("/1").unwrap()) + .unwrap(); + acc.value.1.balance = 2; + + let acc = tree + .key_map + .get_mut(&ChainIndex::from_str("/2").unwrap()) + .unwrap(); + acc.value.1.balance = 3; + + let acc = tree + .key_map + .get_mut(&ChainIndex::from_str("/0/1").unwrap()) + .unwrap(); + acc.value.1.balance = 5; + + let acc = tree + .key_map + .get_mut(&ChainIndex::from_str("/1/0").unwrap()) + .unwrap(); + acc.value.1.balance = 6; + + tree.cleanup_tree_remove_uninit_layered(10); + + let mut key_set_res = HashSet::new(); + key_set_res.insert("/0".to_string()); + key_set_res.insert("/1".to_string()); + key_set_res.insert("/2".to_string()); + key_set_res.insert("/".to_string()); + key_set_res.insert("/0/0".to_string()); + key_set_res.insert("/0/1".to_string()); + key_set_res.insert("/1/0".to_string()); + + let mut key_set = HashSet::new(); + + for key in tree.key_map.keys() { + key_set.insert(key.to_string()); + } + + assert_eq!(key_set, key_set_res); + + let acc = tree + .key_map + .get(&ChainIndex::from_str("/1").unwrap()) + .unwrap(); + assert_eq!(acc.value.1.balance, 2); + + let acc = tree + .key_map + .get(&ChainIndex::from_str("/2").unwrap()) + .unwrap(); + assert_eq!(acc.value.1.balance, 3); + + let acc = tree + .key_map + .get(&ChainIndex::from_str("/0/1").unwrap()) + .unwrap(); + assert_eq!(acc.value.1.balance, 5); + + let acc = tree + .key_map + .get(&ChainIndex::from_str("/1/0").unwrap()) + .unwrap(); + assert_eq!(acc.value.1.balance, 6); + } } diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index ac5ee48..b46c46c 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -89,9 +89,18 @@ impl NSSAUserData { /// Returns the account_id of new account pub fn generate_new_public_transaction_private_key( &mut self, - parent_cci: ChainIndex, - ) -> nssa::AccountId { - self.public_key_tree.generate_new_node(parent_cci).unwrap() + parent_cci: Option, + ) -> (nssa::AccountId, ChainIndex) { + match parent_cci { + Some(parent_cci) => self + .public_key_tree + .generate_new_node(&parent_cci) + .expect("Parent must be present in a tree"), + None => self + .public_key_tree + .generate_new_node_layered() + .expect("Search for new node slot failed"), + } } /// Returns the signing key for public transaction signatures @@ -113,9 +122,18 @@ impl NSSAUserData { /// Returns the account_id of new account pub fn generate_new_privacy_preserving_transaction_key_chain( &mut self, - parent_cci: ChainIndex, - ) -> nssa::AccountId { - self.private_key_tree.generate_new_node(parent_cci).unwrap() + parent_cci: Option, + ) -> (nssa::AccountId, ChainIndex) { + match parent_cci { + Some(parent_cci) => self + .private_key_tree + .generate_new_node(&parent_cci) + .expect("Parent must be present in a tree"), + None => self + .private_key_tree + .generate_new_node_layered() + .expect("Search for new node slot failed"), + } } /// Returns the signing key for public transaction signatures @@ -169,16 +187,8 @@ mod tests { fn test_new_account() { let mut user_data = NSSAUserData::default(); - let account_id_pub = - user_data.generate_new_public_transaction_private_key(ChainIndex::root()); - let account_id_private = - user_data.generate_new_privacy_preserving_transaction_key_chain(ChainIndex::root()); - - let is_private_key_generated = user_data - .get_pub_account_signing_key(&account_id_pub) - .is_some(); - - assert!(is_private_key_generated); + let (account_id_private, _) = user_data + .generate_new_privacy_preserving_transaction_key_chain(Some(ChainIndex::root())); let is_key_chain_generated = user_data.get_private_account(&account_id_private).is_some(); diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 89bec37..c152581 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -25,8 +25,8 @@ pub struct Account { pub nonce: Nonce, } -#[derive(Serialize, Deserialize, Clone)] -#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index 5bf620e..848fe3e 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -10,7 +10,7 @@ use crate::{ #[derive(Serialize, Deserialize)] pub struct PrivacyPreservingCircuitInput { - pub program_output: ProgramOutput, + pub program_outputs: Vec, pub visibility_mask: Vec, pub private_account_nonces: Vec, pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>, diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 8f49724..26ee8de 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer}; use serde::{Deserialize, Serialize}; @@ -8,6 +10,7 @@ use crate::account::{Account, AccountWithMetadata}; pub type ProgramId = [u32; 8]; pub type InstructionData = Vec; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; +pub const MAX_NUMBER_CHAINED_CALLS: usize = 10; pub struct ProgramInput { pub pre_states: Vec, @@ -54,7 +57,9 @@ impl From<(&ProgramId, &PdaSeed)> for AccountId { #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ChainedCall { + /// The program ID of the program to execute pub program_id: ProgramId, + /// The instruction data to pass pub instruction_data: InstructionData, pub pre_states: Vec, pub pda_seeds: Vec, @@ -111,26 +116,34 @@ impl AccountPostState { #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ProgramOutput { + /// The instruction data the program received to produce this output + pub instruction_data: InstructionData, + /// The account pre states the program received to produce this output pub pre_states: Vec, pub post_states: Vec, pub chained_calls: Vec, } -pub fn read_nssa_inputs() -> ProgramInput { +pub fn read_nssa_inputs() -> (ProgramInput, InstructionData) { let pre_states: Vec = env::read(); let instruction_words: InstructionData = env::read(); let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap(); - ProgramInput { - pre_states, - instruction, - } + ( + ProgramInput { + pre_states, + instruction, + }, + instruction_words, + ) } pub fn write_nssa_outputs( + instruction_data: InstructionData, pre_states: Vec, post_states: Vec, ) { let output = ProgramOutput { + instruction_data, pre_states, post_states, chained_calls: Vec::new(), @@ -139,11 +152,13 @@ pub fn write_nssa_outputs( } pub fn write_nssa_outputs_with_chained_call( + instruction_data: InstructionData, pre_states: Vec, post_states: Vec, chained_calls: Vec, ) { let output = ProgramOutput { + instruction_data, pre_states, post_states, chained_calls, @@ -162,32 +177,37 @@ pub fn validate_execution( post_states: &[AccountPostState], executing_program_id: ProgramId, ) -> bool { - // 1. Lengths must match + // 1. Check account ids are all different + if !validate_uniqueness_of_account_ids(pre_states) { + return false; + } + + // 2. Lengths must match if pre_states.len() != post_states.len() { return false; } for (pre, post) in pre_states.iter().zip(post_states) { - // 2. Nonce must remain unchanged + // 3. Nonce must remain unchanged if pre.account.nonce != post.account.nonce { return false; } - // 3. Program ownership changes are not allowed + // 4. Program ownership changes are not allowed if pre.account.program_owner != post.account.program_owner { return false; } let account_program_owner = pre.account.program_owner; - // 4. Decreasing balance only allowed if owned by executing program + // 5. Decreasing balance only allowed if owned by executing program if post.account.balance < pre.account.balance && account_program_owner != executing_program_id { return false; } - // 5. Data changes only allowed if owned by executing program or if account pre state has + // 6. Data changes only allowed if owned by executing program or if account pre state has // default values if pre.account.data != post.account.data && pre.account != Account::default() @@ -196,14 +216,14 @@ pub fn validate_execution( return false; } - // 6. If a post state has default program owner, the pre state must have been a default + // 7. If a post state has default program owner, the pre state must have been a default // account if post.account.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() { return false; } } - // 7. Total balance is preserved + // 8. Total balance is preserved let Some(total_balance_pre_states) = WrappedBalanceSum::from_balances(pre_states.iter().map(|pre| pre.account.balance)) @@ -224,6 +244,17 @@ pub fn validate_execution( true } +fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> bool { + let number_of_accounts = pre_states.len(); + let number_of_account_ids = pre_states + .iter() + .map(|account| &account.account_id) + .collect::>() + .len(); + + number_of_accounts == number_of_account_ids +} + /// Representation of a number as `lo + hi * 2^128`. #[derive(PartialEq, Eq)] struct WrappedBalanceSum { diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index 50afa50..fe02d06 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -6,34 +6,37 @@ use nssa_core::{ }; /// Initializes a default account under the ownership of this program. -fn initialize_account(pre_state: AccountWithMetadata) { +fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState { let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone()); let is_authorized = pre_state.is_authorized; // Continue only if the account to claim has default values if account_to_claim.account() != &Account::default() { - return; + panic!("Account must be uninitialized"); } // Continue only if the owner authorized this operation if !is_authorized { - return; + panic!("Invalid input"); } - // Noop will result in account being claimed for this program - write_nssa_outputs(vec![pre_state], vec![account_to_claim]); + account_to_claim } /// Transfers `balance_to_move` native balance from `sender` to `recipient`. -fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) { +fn transfer( + sender: AccountWithMetadata, + recipient: AccountWithMetadata, + balance_to_move: u128, +) -> Vec { // Continue only if the sender has authorized this operation if !sender.is_authorized { - return; + panic!("Invalid input"); } // Continue only if the sender has enough balance if sender.account.balance < balance_to_move { - return; + panic!("Invalid input"); } // Create accounts post states, with updated balances @@ -57,23 +60,31 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance } }; - write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]); + vec![sender_post, recipient_post] } /// A transfer of balance program. /// To be used both in public and private contexts. fn main() { // Read input accounts. - let ProgramInput { - pre_states, - instruction: balance_to_move, - } = read_nssa_inputs(); + let ( + ProgramInput { + pre_states, + instruction: balance_to_move, + }, + instruction_words, + ) = read_nssa_inputs(); - match (pre_states.as_slice(), balance_to_move) { - ([account_to_claim], 0) => initialize_account(account_to_claim.clone()), + let post_states = match (pre_states.as_slice(), balance_to_move) { + ([account_to_claim], 0) => { + let post = initialize_account(account_to_claim.clone()); + vec![post] + } ([sender, recipient], balance_to_move) => { transfer(sender.clone(), recipient.clone(), balance_to_move) } _ => panic!("invalid params"), - } + }; + + write_nssa_outputs(instruction_words, pre_states, post_states); } diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs index 1c880e2..a0c46a1 100644 --- a/nssa/program_methods/guest/src/bin/pinata.rs +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -44,10 +44,13 @@ impl Challenge { fn main() { // Read input accounts. // It is expected to receive only two accounts: [pinata_account, winner_account] - let ProgramInput { - pre_states, - instruction: solution, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: solution, + }, + instruction_words, + ) = read_nssa_inputs::(); let [pinata, winner] = match pre_states.try_into() { Ok(array) => array, @@ -71,6 +74,7 @@ fn main() { winner_post.balance += PRIZE; write_nssa_outputs( + instruction_words, vec![pinata, winner], vec![ AccountPostState::new(pinata_post), diff --git a/nssa/program_methods/guest/src/bin/pinata_token.rs b/nssa/program_methods/guest/src/bin/pinata_token.rs index 3810485..f988be9 100644 --- a/nssa/program_methods/guest/src/bin/pinata_token.rs +++ b/nssa/program_methods/guest/src/bin/pinata_token.rs @@ -54,10 +54,13 @@ fn main() { // Read input accounts. // It is expected to receive three accounts: [pinata_definition, pinata_token_holding, // winner_token_holding] - let ProgramInput { - pre_states, - instruction: solution, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: solution, + }, + instruction_words, + ) = read_nssa_inputs::(); let [ pinata_definition, @@ -98,6 +101,7 @@ fn main() { }]; write_nssa_outputs_with_chained_call( + instruction_words, vec![ pinata_definition, pinata_token_holding, diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index ac4e212..29162db 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::HashMap; use nssa_core::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, @@ -6,43 +6,114 @@ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, compute_digest_for_path, encryption::Ciphertext, - program::{DEFAULT_PROGRAM_ID, ProgramOutput, validate_execution}, + program::{DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, validate_execution}, }; use risc0_zkvm::{guest::env, serde::to_vec}; fn main() { let PrivacyPreservingCircuitInput { - program_output, + program_outputs, visibility_mask, private_account_nonces, private_account_keys, private_account_auth, - program_id, + mut program_id, } = env::read(); - // Check that `program_output` is consistent with the execution of the corresponding program. - env::verify(program_id, &to_vec(&program_output).unwrap()).unwrap(); + let mut pre_states: Vec = Vec::new(); + let mut state_diff: HashMap = HashMap::new(); - let ProgramOutput { - pre_states, - post_states, - chained_calls, - } = program_output; - - // TODO: implement chained calls for privacy preserving transactions - if !chained_calls.is_empty() { - panic!("Privacy preserving transactions do not support yet chained calls.") + let num_calls = program_outputs.len(); + if num_calls > MAX_NUMBER_CHAINED_CALLS { + panic!("Max chained calls depth is exceeded"); } - // Check that there are no repeated account ids - if !validate_uniqueness_of_account_ids(&pre_states) { - panic!("Repeated account ids found") + let Some(last_program_call) = program_outputs.last() else { + panic!("Program outputs is empty") + }; + + if !last_program_call.chained_calls.is_empty() { + panic!("Call stack is incomplete"); } - // Check that the program is well behaved. - // See the # Programs section for the definition of the `validate_execution` method. - if !validate_execution(&pre_states, &post_states, program_id) { - panic!("Bad behaved program"); + for window in program_outputs.windows(2) { + let caller = &window[0]; + let callee = &window[1]; + + if caller.chained_calls.len() > 1 { + panic!("Privacy Multi-chained calls are not supported yet"); + } + + // TODO: Modify when multi-chain calls are supported in the circuit + let Some(caller_chained_call) = &caller.chained_calls.first() else { + panic!("Expected chained call"); + }; + + // Check that instruction data in caller is the instruction data in callee + if caller_chained_call.instruction_data != callee.instruction_data { + panic!("Invalid instruction data"); + } + + // Check that account pre_states in caller are the ones in calle + if caller_chained_call.pre_states != callee.pre_states { + panic!("Invalid pre states"); + } + } + + for (i, program_output) in program_outputs.iter().enumerate() { + let mut program_output = program_output.clone(); + + // Check that `program_output` is consistent with the execution of the corresponding program. + let program_output_words = + &to_vec(&program_output).expect("program_output must be serializable"); + env::verify(program_id, program_output_words) + .expect("program output must match the program's execution"); + + // Check that the program is well behaved. + // See the # Programs section for the definition of the `validate_execution` method. + if !validate_execution( + &program_output.pre_states, + &program_output.post_states, + program_id, + ) { + panic!("Bad behaved program"); + } + + // The invoked program claims the accounts with default program id. + for post in program_output + .post_states + .iter_mut() + .filter(|post| post.requires_claim()) + { + // The invoked program can only claim accounts with default program id. + if post.account().program_owner == DEFAULT_PROGRAM_ID { + post.account_mut().program_owner = program_id; + } else { + panic!("Cannot claim an initialized account") + } + } + + for (pre, post) in program_output + .pre_states + .iter() + .zip(&program_output.post_states) + { + if let Some(account_pre) = state_diff.get(&pre.account_id) { + if account_pre != &pre.account { + panic!("Invalid input"); + } + } else { + pre_states.push(pre.clone()); + } + state_diff.insert(pre.account_id.clone(), post.account().clone()); + } + + // TODO: Modify when multi-chain calls are supported in the circuit + if let Some(next_chained_call) = &program_output.chained_calls.first() { + program_id = next_chained_call.program_id; + } else if i != program_outputs.len() - 1 { + panic!("Inner call without a chained call found") + }; } let n_accounts = pre_states.len(); @@ -69,10 +140,8 @@ fn main() { // Public account public_pre_states.push(pre_states[i].clone()); - let mut post = post_states[i].account().clone(); - if pre_states[i].is_authorized { - post.nonce += 1; - } + let mut post = state_diff.get(&pre_states[i].account_id).unwrap().clone(); + if post.program_owner == DEFAULT_PROGRAM_ID { // Claim account post.program_owner = program_id; @@ -125,7 +194,8 @@ fn main() { } // Update post-state with new nonce - let mut post_with_updated_values = post_states[i].account().clone(); + let mut post_with_updated_values = + state_diff.get(&pre_states[i].account_id).unwrap().clone(); post_with_updated_values.nonce = *new_nonce; if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { @@ -174,14 +244,3 @@ fn main() { env::commit(&output); } - -fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> bool { - let number_of_accounts = pre_states.len(); - let number_of_account_ids = pre_states - .iter() - .map(|account| account.account_id.clone()) - .collect::>() - .len(); - - number_of_accounts == number_of_account_ids -} diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 1901fb1..739295b 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -18,10 +18,8 @@ use nssa_core::{ // 2. Token transfer // Arguments to this function are: // * Two accounts: [sender_account, recipient_account]. -// * Authorization required: sender_account // * An instruction data byte string of length 23, indicating the total supply with the following layout // [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// * For NFT Master accounts, its entire balance must be transferred. // 3. Initialize account with zero balance // Arguments to this function are: // * Two accounts: [definition_account, account_to_initialize]. @@ -31,138 +29,25 @@ use nssa_core::{ // Arguments to this function are: // * Two accounts: [definition_account, holding_account]. // * Authorization required: holding_account -// * An instruction data byte string of length 23, indicating the balance to burn with the following layout +// * An instruction data byte string of length 23, indicating the balance to burn with the folloiwng layout // [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. // 5. Mint additional supply of tokens tokens to a Token Holding account (thus increasing total supply) // Arguments to this function are: // * Two accounts: [definition_account, holding_account]. // * Authorization required: definition_account -// * An instruction data byte string of length 23, indicating the balance to mint with the following layout +// * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout // [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 6. Print NFT copy from Master NFT -// Arguments to this function are: -// * Two accounts: [master_nft, account_to_initialize]. -// * Authorization required: master_nft -// * An dummy byte string of length 23, with the following layout -// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. -const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0; -const TOKEN_STANDARD_FUNGIBLE_ASSET: u8 = 1; -const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2; -const TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE: u8 = 3; -const METADATA_TYPE_SIMPLE: u8 = 0; -const METADATA_TYPE_EXPANDED: u8 = 1; - -const TOKEN_DEFINITION_DATA_SIZE: usize = 55; - -const TOKEN_HOLDING_STANDARD: u8 = 1; -const TOKEN_HOLDING_NFT_MASTER: u8 = 2; -const TOKEN_HOLDING_NFT_PRINTED_COPY: u8 = 3; +const TOKEN_DEFINITION_TYPE: u8 = 0; +const TOKEN_DEFINITION_DATA_SIZE: usize = 23; +const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_DATA_SIZE: usize = 49; -const CURRENT_VERSION: u8 = 1; - -const TOKEN_METADATA_DATA_SIZE: usize = 463; - -fn is_token_standard_valid(standard: u8) -> bool { - if standard == TOKEN_STANDARD_FUNGIBLE_TOKEN { - true - } else if standard == TOKEN_STANDARD_FUNGIBLE_ASSET { - true - } else if standard == TOKEN_STANDARD_NONFUNGIBLE { - true - } else if standard == TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE { - true - } else { - false - } -} - -fn is_metadata_type_valid(standard: u8) -> bool { - if standard == METADATA_TYPE_SIMPLE { - true - } else if standard == METADATA_TYPE_EXPANDED { - true - } else { - false - } -} - -fn is_token_holding_type_valid(standard: u8) -> bool { - if standard == TOKEN_HOLDING_STANDARD { - true - } else if standard == TOKEN_HOLDING_NFT_MASTER { - true - } else if standard == TOKEN_HOLDING_NFT_PRINTED_COPY { - true - } else { - false - } -} struct TokenDefinition { - account_type: u8, // Token Standard + account_type: u8, name: [u8; 6], total_supply: u128, - metadata_id: AccountId, -} - -impl TokenDefinition { - fn into_data(self) -> Data { - let mut bytes = Vec::::new(); - bytes.extend_from_slice(&[self.account_type]); - bytes.extend_from_slice(&self.name); - bytes.extend_from_slice(&self.total_supply.to_le_bytes()); - bytes.extend_from_slice(&self.metadata_id.to_bytes()); - - if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { - panic!("Invalid Token Definition data"); - } - - Data::try_from(bytes).expect("Invalid data") - } - - fn parse(data: &Data) -> Option { - let data = Vec::::from(data.clone()); - - if data.len() != TOKEN_DEFINITION_DATA_SIZE { - None - } else { - let account_type = data[0]; - let name = data[1..7].try_into().expect("Name must be a 6 bytes"); - let total_supply = u128::from_le_bytes( - data[7..23] - .try_into() - .expect("Total supply must be 16 bytes little-endian"), - ); - let metadata_id = AccountId::new( - data[23..TOKEN_DEFINITION_DATA_SIZE] - .try_into() - .expect("Token Program expects valid Account Id for Metadata"), - ); - - let this = Some(Self { - account_type, - name, - total_supply, - metadata_id: metadata_id.clone(), - }); - - if account_type == //NFTs must have supply 1 - TOKEN_STANDARD_NONFUNGIBLE - && total_supply != 1 - { - None - } else if account_type == //Fungible Tokens do not have metadata. - TOKEN_STANDARD_FUNGIBLE_TOKEN - && metadata_id != AccountId::new([0; 32]) - { - None - } else { - this - } - } - } } struct TokenHolding { @@ -171,24 +56,49 @@ struct TokenHolding { balance: u128, } +impl TokenDefinition { + fn into_data(self) -> Data { + let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; + bytes[0] = self.account_type; + bytes[1..7].copy_from_slice(&self.name); + bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); + bytes + .to_vec() + .try_into() + .expect("23 bytes should fit into Data") + } + + fn parse(data: &[u8]) -> Option { + if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { + None + } else { + let account_type = data[0]; + let name = data[1..7].try_into().unwrap(); + let total_supply = u128::from_le_bytes( + data[7..] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + Some(Self { + account_type, + name, + total_supply, + }) + } + } +} + impl TokenHolding { fn new(definition_id: &AccountId) -> Self { Self { - account_type: TOKEN_HOLDING_STANDARD, + account_type: TOKEN_HOLDING_TYPE, definition_id: definition_id.clone(), balance: 0, } } - fn parse(data: &Data) -> Option { - let data = Vec::::from(data.clone()); - - if data.len() != TOKEN_HOLDING_DATA_SIZE { - return None; - } - - // Check account_type - if !is_token_holding_type_valid(data[0]) { + fn parse(data: &[u8]) -> Option { + if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { return None; } @@ -203,7 +113,6 @@ impl TokenHolding { .try_into() .expect("balance must be 16 bytes little-endian"), ); - Some(Self { definition_id, balance, @@ -212,86 +121,14 @@ impl TokenHolding { } fn into_data(self) -> Data { - if !is_token_holding_type_valid(self.account_type) { - panic!("Invalid Token Holding type"); - } - - let mut bytes = Vec::::new(); - bytes.extend_from_slice(&[self.account_type]); - bytes.extend_from_slice(&self.definition_id.to_bytes()); - bytes.extend_from_slice(&self.balance.to_le_bytes()); - - if bytes.len() != TOKEN_HOLDING_DATA_SIZE { - panic!("Invalid Token Holding data"); - } - - Data::try_from(bytes).expect("Invalid data") - } -} - -struct TokenMetadata { - account_type: u8, - version: u8, - definition_id: AccountId, - uri: [u8; 200], - creators: [u8; 250], - primary_sale_date: u64, //BlockId -} - -impl TokenMetadata { - fn into_data(self) -> Data { - if !is_metadata_type_valid(self.account_type) { - panic!("Invalid Metadata type"); - } - - let mut bytes = Vec::::new(); - bytes.extend_from_slice(&[self.account_type]); - bytes.extend_from_slice(&[self.version]); - bytes.extend_from_slice(&self.definition_id.to_bytes()); - bytes.extend_from_slice(&self.uri); - bytes.extend_from_slice(&self.creators); - bytes.extend_from_slice(&self.primary_sale_date.to_le_bytes()); - - if bytes.len() != TOKEN_METADATA_DATA_SIZE { - panic!("Invalid Token Definition data length"); - } - - Data::try_from(bytes).expect("Invalid data") - } - - fn parse(data: &Data) -> Option { - let data = Vec::::from(data.clone()); - - if data.len() != TOKEN_METADATA_DATA_SIZE || !is_metadata_type_valid(data[0]) { - None - } else { - let account_type = data[0]; - let version = data[1]; - let definition_id = AccountId::new( - data[2..34] - .try_into() - .expect("Token Program expects valid Account Id for Metadata"), - ); - let uri: [u8; 200] = data[34..234] - .try_into() - .expect("Token Program expects valid uri for Metadata"); - let creators: [u8; 250] = data[234..484] - .try_into() - .expect("Token Program expects valid creators for Metadata"); - let primary_sale_date = u64::from_le_bytes( - data[484..TOKEN_METADATA_DATA_SIZE] - .try_into() - .expect("Token Program expects valid blockid for Metadata"), - ); - Some(Self { - account_type, - version, - definition_id, - uri, - creators, - primary_sale_date, - }) - } + let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE]; + bytes[0] = self.account_type; + bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); + bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); + bytes + .to_vec() + .try_into() + .expect("33 bytes should fit into Data") } } @@ -302,13 +139,9 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec Vec Vec (TokenHolding, TokenHolding) { - let mut sender_holding = sender_holding; - let mut recipient_holding = recipient_holding; - - if sender_holding.balance < balance_to_move { - panic!("Insufficient balance"); - } - - sender_holding.balance = sender_holding - .balance - .checked_sub(balance_to_move) - .expect("Checked above"); - recipient_holding.balance = recipient_holding - .balance - .checked_add(balance_to_move) - .expect("Recipient balance overflow"); - - recipient_holding.account_type = sender_holding.account_type; - - (sender_holding, recipient_holding) -} - -fn nft_master_transfer( - sender_holding: TokenHolding, - recipient_holding: TokenHolding, - balance_to_move: u128, -) -> (TokenHolding, TokenHolding) { - let mut sender_holding = sender_holding; - let mut recipient_holding = recipient_holding; - - if recipient_holding.balance != 0 { - panic!("Invalid balance in recipient account for NFT transfer"); - } - - if sender_holding.balance != balance_to_move { - panic!("Invalid balance for NFT Master transfer"); - } - - sender_holding.balance = 0; - recipient_holding.balance = balance_to_move; - recipient_holding.account_type = sender_holding.account_type; - - (sender_holding, recipient_holding) -} - fn new_definition( pre_states: &[AccountWithMetadata], name: [u8; 6], @@ -403,7 +194,6 @@ fn new_definition( if pre_states.len() != 2 { panic!("Invalid number of input accounts"); } - let definition_target_account = &pre_states[0]; let holding_target_account = &pre_states[1]; @@ -416,14 +206,13 @@ fn new_definition( } let token_definition = TokenDefinition { - account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + account_type: TOKEN_DEFINITION_TYPE, name, total_supply, - metadata_id: AccountId::new([0; 32]), }; let token_holding = TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, + account_type: TOKEN_HOLDING_TYPE, definition_id: definition_target_account.account_id.clone(), balance: total_supply, }; @@ -440,103 +229,6 @@ fn new_definition( ] } -fn new_definition_with_metadata( - pre_states: &[AccountWithMetadata], - name: [u8; 6], - total_supply: u128, - token_standard: u8, - metadata_standard: u8, - metadata_values: &Data, -) -> Vec { - if pre_states.len() != 3 { - panic!("Invalid number of input accounts"); - } - - let definition_target_account = &pre_states[0]; - let metadata_target_account = &pre_states[1]; - let holding_target_account = &pre_states[2]; - - if definition_target_account.account != Account::default() { - panic!("Definition target account must have default values"); - } - - if metadata_target_account.account != Account::default() { - panic!("Metadata target account must have default values"); - } - - if holding_target_account.account != Account::default() { - panic!("Holding target account must have default values"); - } - - if !is_token_standard_valid(token_standard) { - panic!("Invalid Token Standard provided"); - } - - if !is_metadata_type_valid(metadata_standard) { - panic!("Invalid Metadata Standadard provided"); - } - - if !valid_total_supply_for_token_standard(total_supply, token_standard) { - panic!("Invalid total supply for the specified token supply"); - } - - let token_definition = TokenDefinition { - account_type: token_standard, - name, - total_supply, - metadata_id: metadata_target_account.account_id.clone(), - }; - - let token_holding = TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: definition_target_account.account_id.clone(), - balance: total_supply, - }; - - if metadata_values.len() != 450 { - panic!("Metadata values data should be 450 bytes"); - } - - let uri: [u8; 200] = metadata_values[0..200] - .try_into() - .expect("Token program expects valid uri for Metadata"); - let creators: [u8; 250] = metadata_values[200..450] - .try_into() - .expect("Token program expects valid creators for Metadata"); - - let token_metadata = TokenMetadata { - account_type: metadata_standard, - version: CURRENT_VERSION, - definition_id: definition_target_account.account_id.clone(), - uri, - creators, - primary_sale_date: 0u64, //TODO: future works to implement this - }; - - let mut definition_target_account_post = definition_target_account.account.clone(); - definition_target_account_post.data = token_definition.into_data(); - - let mut holding_target_account_post = holding_target_account.account.clone(); - holding_target_account_post.data = token_holding.into_data(); - - let mut metadata_target_account_post = metadata_target_account.account.clone(); - metadata_target_account_post.data = token_metadata.into_data(); - - vec![ - AccountPostState::new_claimed(definition_target_account_post), - AccountPostState::new_claimed(holding_target_account_post), - AccountPostState::new_claimed(metadata_target_account_post), - ] -} - -fn valid_total_supply_for_token_standard(total_supply: u128, token_standard: u8) -> bool { - if token_standard == TOKEN_STANDARD_NONFUNGIBLE && total_supply != 1 { - false - } else { - true - } -} - fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of accounts"); @@ -575,17 +267,17 @@ fn burn(pre_states: &[AccountWithMetadata], balance_to_burn: u128) -> Vec Vec Vec bool { - if account_type == TOKEN_STANDARD_NONFUNGIBLE { - false - } else { - true - } -} - fn mint_additional_supply( pre_states: &[AccountWithMetadata], amount_to_mint: u128, @@ -652,12 +335,8 @@ fn mint_additional_supply( TokenHolding::parse(&token_holding.account.data).expect("Holding account must be valid") }; - if !is_mintable(definition_values.account_type) { - panic!("Token Definition's standard does not permit minting additional supply"); - } - if definition.account_id != token_holding_values.definition_id { - panic!("Mismatch Token Definition and Token Holding"); + panic!("Mismatch token definition and token holding"); } let token_holding_post_data = TokenHolding { @@ -678,7 +357,6 @@ fn mint_additional_supply( account_type: definition_values.account_type, name: definition_values.name, total_supply: post_total_supply, - metadata_id: definition_values.metadata_id, }; let post_definition = { @@ -701,72 +379,19 @@ fn mint_additional_supply( vec![post_definition, token_holding_post] } -fn print_nft(pre_states: &[AccountWithMetadata]) -> Vec { - if pre_states.len() != 2 { - panic!("Invalid number of accounts"); - } +type Instruction = [u8; 23]; - let master_account = &pre_states[0]; - let printed_account = &pre_states[1]; - - if !master_account.is_authorized { - panic!("Master NFT Account must be authorized"); - } - - if printed_account.account != Account::default() { - panic!("Printed Account must be uninitialized"); - } - - let mut master_account_data = - TokenHolding::parse(&master_account.account.data).expect("Invalid Token Holding data"); - - if master_account_data.account_type != TOKEN_HOLDING_NFT_MASTER { - panic!("Invalid Token Holding provided as NFT Master Account"); - } - - if master_account_data.balance < 2 { - panic!("Insufficient balance to print another NFT copy"); - } - - let definition_id = master_account_data.definition_id.clone(); - - let post_master_account = { - let mut this = master_account.account.clone(); - master_account_data.balance -= 1; - this.data = master_account_data.into_data(); - AccountPostState::new(this) - }; - - let post_printed_account = { - let mut this = printed_account.account.clone(); - - let printed_data = TokenHolding { - account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, - definition_id, - balance: 1, - }; - - this.data = TokenHolding::into_data(printed_data); - - AccountPostState::new_claimed(this) - }; - - vec![post_master_account, post_printed_account] -} - -type Instruction = Vec; fn main() { - let ProgramInput { - pre_states, - instruction, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction, + }, + instruction_words, + ) = read_nssa_inputs::(); let post_states = match instruction[0] { 0 => { - if instruction.len() != 23 { - panic!("Invalid instruction length"); - } - // Parse instruction let total_supply = u128::from_le_bytes( instruction[1..17] @@ -782,10 +407,6 @@ fn main() { new_definition(&pre_states, name, total_supply) } 1 => { - if instruction.len() != 23 { - panic!("Invalid instruction length"); - } - // Parse instruction let balance_to_move = u128::from_le_bytes( instruction[1..17] @@ -801,10 +422,6 @@ fn main() { transfer(&pre_states, balance_to_move) } 2 => { - if instruction.len() != 23 { - panic!("Invalid instruction length"); - } - // Initialize account if instruction[1..] != [0; 22] { panic!("Invalid instruction for initialize account"); @@ -812,11 +429,6 @@ fn main() { initialize_account(&pre_states) } 3 => { - if instruction.len() != 23 { - panic!("Invalid instruction length"); - } - - // Parse instruction let balance_to_burn = u128::from_le_bytes( instruction[1..17] .try_into() @@ -831,11 +443,6 @@ fn main() { burn(&pre_states, balance_to_burn) } 4 => { - if instruction.len() != 23 { - panic!("Invalid instruction length"); - } - - // Parse instruction let balance_to_mint = u128::from_le_bytes( instruction[1..17] .try_into() @@ -849,542 +456,22 @@ fn main() { // Execute mint_additional_supply(&pre_states, balance_to_mint) } - 5 => { - if instruction.len() != 474 { - panic!("Invalid instruction length") - } - - // Parse instruction - let total_supply = u128::from_le_bytes( - instruction[1..17] - .try_into() - .expect("Total supply must be 16 bytes little-endian"), - ); - let name = instruction[17..23] - .try_into() - .expect("Name must be 6 bytes long"); - assert_ne!(name, [0; 6]); - let token_standard = instruction[23]; - let metadata_standard = instruction[24]; - let metadata_values: Data = - Data::try_from(instruction[25..474].to_vec()).expect("Invalid metadata"); - - // Execute - new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ) - } - 6 => { - if instruction.len() != 23 { - panic!("Invalid instruction length"); - } - - // Initialize account - if instruction[1..] != [0; 22] { - panic!("Invalid instruction for initialize account"); - } - - print_nft(&pre_states) - } _ => panic!("Invalid instruction"), }; - write_nssa_outputs(pre_states, post_states); + write_nssa_outputs(instruction_words, pre_states, post_states); } #[cfg(test)] mod tests { - use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data}; + use nssa_core::account::{Account, AccountId, AccountWithMetadata}; use crate::{ - TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_NFT_MASTER, - TOKEN_HOLDING_NFT_PRINTED_COPY, TOKEN_HOLDING_STANDARD, TOKEN_STANDARD_FUNGIBLE_ASSET, - TOKEN_STANDARD_FUNGIBLE_TOKEN, TOKEN_STANDARD_NONFUNGIBLE, TokenDefinition, TokenHolding, - burn, initialize_account, mint_additional_supply, new_definition, - new_definition_with_metadata, print_nft, transfer, + TOKEN_DEFINITION_DATA_SIZE, TOKEN_DEFINITION_TYPE, TOKEN_HOLDING_DATA_SIZE, + TOKEN_HOLDING_TYPE, TokenDefinition, TokenHolding, burn, initialize_account, + mint_additional_supply, new_definition, transfer, }; - enum BalanceEnum { - InitSupply, - HoldingBalance, - InitSupplyBurned, - HoldingBalanceBurned, - BurnSuccess, - BurnInsufficient, - MintSuccess, - InitSupplyMint, - HoldingBalanceMint, - MintOverflow, - RecipientPostTransfer, - RecipientUninitPostTransfer, - SenderPostTransfer, - TransferAmount, - PrintableCopies, - } - - enum AccountsEnum { - DefinitionAccountAuth, - DefinitionAccountNotAuth, - HoldingDiffDef, - HoldingSameDefAuth, - HoldingSameDefNotAuth, - HoldingSameDefNotAuthOverflow, - DefinitionAccountPostBurn, - HoldingAccountPostBurn, - InitMint, - DefinitionAccountMint, - HoldingSameDefMint, - HoldingSameDefAuthLargeBalance, - DefinitionAccountAuthNonFungible, - DefinitionAccountUninit, - HoldingAccountUninit, - HoldingAccountInit, - DefinitionAccountUnclaimed, - HoldingAccountUnclaimed, - HoldingAccount2Init, - HoldingAccount2InitPostTransfer, - HoldingAccount2UninitPostTransfer, - HoldingAccountInitPostTransfer, - HoldingAccountMasterNFT, - HoldingAccountMasterNFTInsufficientBalance, - HoldingAccountMasterNFTAfterPrint, - HoldingAccountPrintedNFT, - HoldingAccountMasterNFTTransferredTo, - HoldingAccountMasterNFTPostTransfer, - } - - enum IdEnum { - PoolDefinitionId, - PoolDefinitionIdDiff, - HoldingId, - Holding2Id, - MetadataId, - } - - fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { - match selection { - AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: AccountId::new([0; 32]), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: AccountId::new([0; 32]), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingDiffDef => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), - metadata_id: AccountId::new([0; 32]), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountUninit => AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::InitMint => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintSuccess), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefMint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountMint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), - metadata_id: AccountId::new([0; 32]), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintOverflow), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountAuthNonFungible => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_STANDARD_NONFUNGIBLE, - name: [2; 6], - total_supply: 1, - metadata_id: AccountId::new([0; 32]), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountUninit => AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountInit => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::DefinitionAccountUnclaimed => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - metadata_id: AccountId::new([0; 32]), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountUnclaimed => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccount2Init => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::HoldingAccount2InitPostTransfer => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::RecipientPostTransfer), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::HoldingAccountInitPostTransfer => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::SenderPostTransfer), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccount2UninitPostTransfer => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_STANDARD, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor( - BalanceEnum::RecipientUninitPostTransfer, - ), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::HoldingAccountMasterNFT => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::PrintableCopies), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountMasterNFTInsufficientBalance => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: 1, - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountMasterNFTAfterPrint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::PrintableCopies) - 1, - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountPrintedNFT => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: 1, - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingAccountMasterNFTTransferredTo => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::PrintableCopies), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::Holding2Id), - }, - AccountsEnum::HoldingAccountMasterNFTPostTransfer => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_NFT_MASTER, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: 0, - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - _ => panic!("Invalid selection"), - } - } - - fn helper_balance_constructor(selection: BalanceEnum) -> u128 { - match selection { - BalanceEnum::InitSupply => 100_000, - BalanceEnum::HoldingBalance => 1_000, - BalanceEnum::InitSupplyBurned => 99_500, - BalanceEnum::HoldingBalanceBurned => 500, - BalanceEnum::BurnSuccess => 500, - BalanceEnum::BurnInsufficient => 1_500, - BalanceEnum::MintSuccess => 50_000, - BalanceEnum::InitSupplyMint => 150_000, - BalanceEnum::HoldingBalanceMint => 51_000, - BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, - BalanceEnum::SenderPostTransfer => 95_000, - BalanceEnum::RecipientPostTransfer => 105_000, - BalanceEnum::RecipientUninitPostTransfer => 5_000, - BalanceEnum::TransferAmount => 5_000, - BalanceEnum::PrintableCopies => 10, - _ => panic!("Invalid selection"), - } - } - - fn helper_id_constructor(selection: IdEnum) -> AccountId { - match selection { - IdEnum::PoolDefinitionId => AccountId::new([15; 32]), - IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), - IdEnum::HoldingId => AccountId::new([17; 32]), - IdEnum::MetadataId => AccountId::new([42; 32]), - IdEnum::Holding2Id => AccountId::new([31; 32]), - } - } - #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_1() { @@ -1464,25 +551,39 @@ mod tests { #[test] fn test_new_definition_with_valid_inputs_succeeds() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountUninit), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: AccountId::new([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + ]), + }, + AccountWithMetadata { + account: Account { + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([2; 32]), + }, ]; - let post_states = new_definition( - &pre_states, - [2u8; 6], - helper_balance_constructor(BalanceEnum::InitSupply), - ); - + let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); - assert!( - *definition_account.account() - == helper_account_constructor(AccountsEnum::DefinitionAccountUnclaimed).account + assert_eq!( + definition_account.account().data.as_ref(), + &[ + 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 + ] ); - - assert!( - *holding_account.account() - == helper_account_constructor(AccountsEnum::HoldingAccountUnclaimed).account + assert_eq!( + holding_account.account().data.as_ref(), + &[ + 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 + ] ); } @@ -1523,13 +624,14 @@ mod tests { #[should_panic(expected = "Invalid sender data")] #[test] fn test_transfer_invalid_instruction_type_should_fail() { - let invalid_type = TOKEN_HOLDING_STANDARD ^ 1; + let invalid_type = TOKEN_HOLDING_TYPE ^ 1; let pre_states = vec![ AccountWithMetadata { account: Account { - // First byte should be `TOKEN_HOLDING_STANDARD` for token holding accounts - data: Data::try_from(vec![invalid_type; TOKEN_HOLDING_DATA_SIZE]) - .expect("Invalid data"), + // First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts + data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE] + .try_into() + .unwrap(), ..Account::default() }, is_authorized: true, @@ -1551,7 +653,7 @@ mod tests { AccountWithMetadata { account: Account { // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1].try_into().unwrap(), ..Account::default() }, is_authorized: true, @@ -1573,7 +675,7 @@ mod tests { AccountWithMetadata { account: Account { // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1].try_into().unwrap(), ..Account::default() }, is_authorized: true, @@ -1592,8 +694,27 @@ mod tests { #[test] fn test_transfer_with_different_definition_ids_should_fail() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - helper_account_constructor(AccountsEnum::HoldingDiffDef), + AccountWithMetadata { + account: Account { + data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account { + data: [1] + .into_iter() + .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) + .collect::>() + .try_into() + .unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, ]; let _post_states = transfer(&pre_states, 10); } @@ -1602,28 +723,46 @@ mod tests { #[test] fn test_transfer_with_insufficient_balance_should_fail() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefMint), + AccountWithMetadata { + account: Account { + // Account with balance 37 + data: [1; TOKEN_HOLDING_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(37)) + .collect::>() + .try_into() + .unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account { + data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, ]; // Attempt to transfer 38 tokens - let _post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnInsufficient), - ); + let _post_states = transfer(&pre_states, 38); } #[should_panic(expected = "Sender authorization is missing")] #[test] fn test_transfer_without_sender_authorization_should_fail() { - let mut def_data = Vec::::new(); - def_data.extend_from_slice(&[1; TOKEN_DEFINITION_DATA_SIZE - 16]); - def_data.extend_from_slice(&u128::to_le_bytes(37)); - let pre_states = vec![ AccountWithMetadata { account: Account { // Account with balance 37 - data: Data::try_from(def_data).unwrap(), + data: [1; TOKEN_HOLDING_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(37)) + .collect::>() + .try_into() + .unwrap(), ..Account::default() }, is_authorized: false, @@ -1631,7 +770,7 @@ mod tests { }, AccountWithMetadata { account: Account { - data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), ..Account::default() }, is_authorized: true, @@ -1644,108 +783,327 @@ mod tests { #[test] fn test_transfer_with_valid_inputs_succeeds() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountInit), - helper_account_constructor(AccountsEnum::HoldingAccount2Init), + AccountWithMetadata { + account: Account { + // Account with balance 37 + data: [1; TOKEN_HOLDING_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(37)) + .collect::>() + .try_into() + .unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account { + // Account with balance 255 + data: [1; TOKEN_HOLDING_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(255)) + .collect::>() + .try_into() + .unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, ]; - let post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::TransferAmount), - ); + let post_states = transfer(&pre_states, 11); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); - - assert!( - *sender_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account + assert_eq!( + sender_post.account().data.as_ref(), + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] ); - assert!( - *recipient_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccount2InitPostTransfer) - .account - ); - } - - #[should_panic(expected = "Invalid balance for NFT Master transfer")] - #[test] - fn test_transfer_with_master_nft_invalid_balance() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - ]; - let post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::TransferAmount), - ); - let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); - - assert!( - *sender_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account - ); - assert!( - *recipient_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccount2InitPostTransfer) - .account - ); - } - - #[should_panic(expected = "Invalid balance in recipient account for NFT transfer")] - #[test] - fn test_transfer_with_master_nft_invalid_recipient_balance() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTTransferredTo), - ]; - let _post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::PrintableCopies), - ); - } - - #[test] - fn test_transfer_with_master_nft_success() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - ]; - let post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::PrintableCopies), - ); - let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); - - assert!( - *sender_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTPostTransfer) - .account - ); - assert!( - *recipient_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTTransferredTo) - .account + assert_eq!( + recipient_post.account().data.as_ref(), + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] ); } #[test] fn test_token_initialize_account_succeeds() { let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountInit), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + AccountWithMetadata { + account: Account { + // Definition ID with + data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(1000)) + .collect::>() + .try_into() + .unwrap(), + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: AccountId::new([2; 32]), + }, ]; - let post_states = transfer( - &pre_states, - helper_balance_constructor(BalanceEnum::TransferAmount), + let post_states = initialize_account(&pre_states); + let [definition, holding] = post_states.try_into().ok().unwrap(); + assert_eq!( + definition.account().data.as_ref(), + pre_states[0].account.data.as_ref() ); - let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + assert_eq!( + holding.account().data.as_ref(), + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } - assert!( - *sender_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountInitPostTransfer).account - ); - assert!( - *recipient_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccount2UninitPostTransfer) - .account - ); + enum BalanceEnum { + InitSupply, + HoldingBalance, + InitSupplyBurned, + HoldingBalanceBurned, + BurnSuccess, + BurnInsufficient, + MintSuccess, + InitSupplyMint, + HoldingBalanceMint, + MintOverflow, + } + + enum AccountsEnum { + DefinitionAccountAuth, + DefinitionAccountNotAuth, + HoldingDiffDef, + HoldingSameDefAuth, + HoldingSameDefNotAuth, + HoldingSameDefNotAuthOverflow, + DefinitionAccountPostBurn, + HoldingAccountPostBurn, + Uninit, + InitMint, + DefinitionAccountMint, + HoldingSameDefMint, + HoldingSameDefAuthLargeBalance, + } + + enum IdEnum { + PoolDefinitionId, + PoolDefinitionIdDiff, + HoldingId, + } + + fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { + match selection { + AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_DEFINITION_TYPE, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_DEFINITION_TYPE, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingDiffDef => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalance), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::InitSupply), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_DEFINITION_TYPE, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::Uninit => AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::InitMint => AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::MintSuccess), + }), + nonce: 0, + }, + is_authorized: false, + account_id: helper_id_constructor(IdEnum::HoldingId), + }, + AccountsEnum::HoldingSameDefMint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::DefinitionAccountMint => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_DEFINITION_TYPE, + name: [2; 6], + total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_TYPE, + definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), + balance: helper_balance_constructor(BalanceEnum::MintOverflow), + }), + nonce: 0, + }, + is_authorized: true, + account_id: helper_id_constructor(IdEnum::PoolDefinitionId), + }, + _ => panic!("Invalid selection"), + } + } + + fn helper_balance_constructor(selection: BalanceEnum) -> u128 { + match selection { + BalanceEnum::InitSupply => 100_000, + BalanceEnum::HoldingBalance => 1_000, + BalanceEnum::InitSupplyBurned => 99_500, + BalanceEnum::HoldingBalanceBurned => 500, + BalanceEnum::BurnSuccess => 500, + BalanceEnum::BurnInsufficient => 1_500, + BalanceEnum::MintSuccess => 50_000, + BalanceEnum::InitSupplyMint => 150_000, + BalanceEnum::HoldingBalanceMint => 51_000, + BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, + _ => panic!("Invalid selection"), + } + } + + fn helper_id_constructor(selection: IdEnum) -> AccountId { + match selection { + IdEnum::PoolDefinitionId => AccountId::new([15; 32]), + IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), + IdEnum::HoldingId => AccountId::new([17; 32]), + } } #[test] @@ -1761,7 +1119,7 @@ mod tests { } #[test] - #[should_panic(expected = "Mismatch Token Definition and Token Holding")] + #[should_panic(expected = "Mismatch token definition and token holding")] fn test_burn_mismatch_def() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), @@ -1838,7 +1196,7 @@ mod tests { #[test] #[should_panic(expected = "Invalid number of accounts")] - fn test_mint_invalid_number_of_accounts_1() { + fn test_mint_invalid_number_of_accounts() { let pre_states = vec![helper_account_constructor( AccountsEnum::DefinitionAccountAuth, )]; @@ -1848,20 +1206,6 @@ mod tests { ); } - #[test] - #[should_panic(expected = "Invalid number of accounts")] - fn test_mint_invalid_number_of_accounts_2() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefMint), - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - } - #[test] #[should_panic(expected = "Holding account must be valid")] fn test_mint_not_valid_holding_account() { @@ -1875,19 +1219,6 @@ mod tests { ); } - #[test] - #[should_panic(expected = "Definition account must be valid")] - fn test_mint_not_valid_definition_account() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), - ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - } - #[test] #[should_panic(expected = "Definition authorization is missing")] fn test_mint_missing_authorization() { @@ -1902,7 +1233,7 @@ mod tests { } #[test] - #[should_panic(expected = "Mismatch Token Definition and Token Holding")] + #[should_panic(expected = "Mismatch token definition and token holding")] fn test_mint_mismatched_token_definition() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), @@ -1942,7 +1273,7 @@ mod tests { fn test_mint_uninit_holding_success() { let pre_states = vec![ helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), + helper_account_constructor(AccountsEnum::Uninit), ]; let post_states = mint_additional_supply( &pre_states, @@ -1987,483 +1318,4 @@ mod tests { helper_balance_constructor(BalanceEnum::MintOverflow), ); } - - #[test] - #[should_panic( - expected = "Token Definition's standard does not permit minting additional supply" - )] - fn test_mint_cannot_mint_unmintable_tokens() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuthNonFungible), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), - ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - } - - #[should_panic(expected = "Invalid number of input accounts")] - #[test] - fn test_call_new_definition_metadata_with_invalid_number_of_accounts_1() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Invalid number of input accounts")] - #[test] - fn test_call_new_definition_metadata_with_invalid_number_of_accounts_2() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Invalid number of input accounts")] - #[test] - fn test_call_new_definition_metadata_with_invalid_number_of_accounts_3() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([4; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Definition target account must have default values")] - #[test] - fn test_call_new_definition_metadata_with_init_definition() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Metadata target account must have default values")] - #[test] - fn test_call_new_definition_metadata_with_init_metadata() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - helper_account_constructor(AccountsEnum::HoldingSameDefMint), - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Holding target account must have default values")] - #[test] - fn test_call_new_definition_metadata_with_init_holding() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - helper_account_constructor(AccountsEnum::HoldingSameDefMint), - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Metadata values data should be 450 bytes")] - #[test] - fn test_call_new_definition_metadata_with_too_short_metadata_length() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 449].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Metadata values data should be 450 bytes")] - #[test] - fn test_call_new_definition_metadata_with_too_long_metadata_length() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 451].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Invalid Token Standard provided")] - #[test] - fn test_call_new_definition_metadata_with_invalid_token_standard() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 14u8; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Invalid Metadata Standadard provided")] - #[test] - fn test_call_new_definition_metadata_with_invalid_metadata_standard() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = 0u8; - let metadata_standard = 14u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Invalid total supply for the specified token supply")] - #[test] - fn test_call_new_definition_metadata_invalid_supply_for_nonfungible() { - let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; - let total_supply = 15u128; - let token_standard = TOKEN_STANDARD_NONFUNGIBLE; - let metadata_standard = 0u8; - let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); - - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = new_definition_with_metadata( - &pre_states, - name, - total_supply, - token_standard, - metadata_standard, - &metadata_values, - ); - } - - #[should_panic(expected = "Invalid number of accounts")] - #[test] - fn test_print_nft_invalid_number_of_accounts_1() { - let pre_states = vec![helper_account_constructor( - AccountsEnum::HoldingAccountMasterNFT, - )]; - let _post_states = print_nft(&pre_states); - } - - #[should_panic(expected = "Invalid number of accounts")] - #[test] - fn test_print_nft_invalid_number_of_accounts_2() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - ]; - let _post_states = print_nft(&pre_states); - } - - #[should_panic(expected = "Master NFT Account must be authorized")] - #[test] - fn test_print_nft_master_account_must_be_authorized() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - ]; - let _post_states = print_nft(&pre_states); - } - - #[should_panic(expected = "Printed Account must be uninitialized")] - #[test] - fn test_print_nft_print_account_initialized() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountInit), - ]; - let _post_states = print_nft(&pre_states); - } - - #[should_panic(expected = "Invalid Token Holding data")] - #[test] - fn test_print_nft_master_nft_invalid_token_holding() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - ]; - let _post_states = print_nft(&pre_states); - } - - #[should_panic(expected = "Invalid Token Holding provided as NFT Master Account")] - #[test] - fn test_print_nft_master_nft_not_nft_master_account() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountInit), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - ]; - let _post_states = print_nft(&pre_states); - } - - #[should_panic(expected = "Insufficient balance to print another NFT copy")] - #[test] - fn test_print_nft_master_nft_insufficient_balance() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTInsufficientBalance), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - ]; - let _post_states = print_nft(&pre_states); - } - - #[test] - fn test_print_nft_success() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::HoldingAccountMasterNFT), - helper_account_constructor(AccountsEnum::HoldingAccountUninit), - ]; - let post_states = print_nft(&pre_states); - - let post_master_nft = post_states[0].account(); - let post_printed = post_states[1].account(); - - assert!( - *post_master_nft - == helper_account_constructor(AccountsEnum::HoldingAccountMasterNFTAfterPrint) - .account - ); - assert!( - *post_printed - == helper_account_constructor(AccountsEnum::HoldingAccountPrintedNFT).account - ); - } } diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 4ef02b3..95933a3 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,9 +1,11 @@ +use std::collections::HashMap; + use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::AccountWithMetadata, - program::{InstructionData, ProgramOutput}, + program::{InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -11,12 +13,35 @@ use crate::{ error::NssaError, program::Program, program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID}, + state::MAX_NUMBER_CHAINED_CALLS, }; /// Proof of the privacy preserving execution circuit #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Proof(pub(crate) Vec); +#[derive(Clone)] +pub struct ProgramWithDependencies { + pub program: Program, + // TODO: avoid having a copy of the bytecode of each dependency. + pub dependencies: HashMap, +} + +impl ProgramWithDependencies { + pub fn new(program: Program, dependencies: HashMap) -> Self { + Self { + program, + dependencies, + } + } +} + +impl From for ProgramWithDependencies { + fn from(program: Program) -> Self { + ProgramWithDependencies::new(program, HashMap::new()) + } +} + /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution /// circuit pub fn execute_and_prove( @@ -26,27 +51,64 @@ pub fn execute_and_prove( private_account_nonces: &[u128], private_account_keys: &[(NullifierPublicKey, SharedSecretKey)], private_account_auth: &[(NullifierSecretKey, MembershipProof)], - program: &Program, + program_with_dependencies: &ProgramWithDependencies, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { - let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; + let mut program = &program_with_dependencies.program; + let dependencies = &program_with_dependencies.dependencies; + let mut instruction_data = instruction_data.clone(); + let mut pre_states = pre_states.to_vec(); + let mut env_builder = ExecutorEnv::builder(); + let mut program_outputs = Vec::new(); - let program_output: ProgramOutput = inner_receipt - .journal - .decode() - .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; + for _i in 0..MAX_NUMBER_CHAINED_CALLS { + let inner_receipt = execute_and_prove_program(program, &pre_states, &instruction_data)?; + + let program_output: ProgramOutput = inner_receipt + .journal + .decode() + .map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?; + + // TODO: remove clone + program_outputs.push(program_output.clone()); + + // Prove circuit. + env_builder.add_assumption(inner_receipt); + + // TODO: Remove when multi-chain calls are supported in the circuit + assert!(program_output.chained_calls.len() <= 1); + // TODO: Modify when multi-chain calls are supported in the circuit + if let Some(next_call) = program_output.chained_calls.first() { + program = dependencies + .get(&next_call.program_id) + .ok_or(NssaError::InvalidProgramBehavior)?; + instruction_data = next_call.instruction_data.clone(); + // Build post states with metadata for next call + let mut post_states_with_metadata = Vec::new(); + for (pre, post) in program_output + .pre_states + .iter() + .zip(program_output.post_states) + { + let mut post_with_metadata = pre.clone(); + post_with_metadata.account = post.account().clone(); + post_states_with_metadata.push(post_with_metadata); + } + + pre_states = next_call.pre_states.clone(); + } else { + break; + } + } let circuit_input = PrivacyPreservingCircuitInput { - program_output, + program_outputs, visibility_mask: visibility_mask.to_vec(), private_account_nonces: private_account_nonces.to_vec(), private_account_keys: private_account_keys.to_vec(), private_account_auth: private_account_auth.to_vec(), - program_id: program.id(), + program_id: program_with_dependencies.program.id(), }; - // Prove circuit. - let mut env_builder = ExecutorEnv::builder(); - env_builder.add_assumption(inner_receipt); env_builder.write(&circuit_input).unwrap(); let env = env_builder.build().unwrap(); let prover = default_prover(); @@ -133,7 +195,7 @@ mod tests { let expected_sender_post = Account { program_owner: program.id(), balance: 100 - balance_to_move, - nonce: 1, + nonce: 0, data: Data::default(), }; @@ -156,7 +218,7 @@ mod tests { &[0xdeadbeef], &[(recipient_keys.npk(), shared_secret.clone())], &[], - &Program::authenticated_transfer_program(), + &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -257,7 +319,7 @@ mod tests { sender_keys.nsk, commitment_set.get_proof_for(&commitment_sender).unwrap(), )], - &program, + &program.into(), ) .unwrap(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index f91a007..1865248 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -7,14 +7,14 @@ use serde::Serialize; use crate::{ error::NssaError, - program_methods::{AUTHENTICATED_TRANSFER_ELF, MODIFIED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}, + program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}, }; /// Maximum number of cycles for a public execution. /// TODO: Make this variable when fees are implemented const MAX_NUM_CYCLES_PUBLIC_EXECUTION: u64 = 1024 * 1024 * 32; // 32M cycles -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Program { id: ProgramId, elf: Vec, @@ -95,12 +95,6 @@ impl Program { // `program_methods` Self::new(TOKEN_ELF.to_vec()).unwrap() } - - pub fn modified_transfer_program() -> Self { - // This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of - // `program_methods` - Self::new(MODIFIED_TRANSFER_ELF.to_vec()).unwrap() - } } // TODO: Testnet only. Refactor to prevent compilation on mainnet. @@ -227,6 +221,13 @@ mod tests { elf: CLAIMER_ELF.to_vec(), } } + + pub fn modified_transfer_program() -> Self { + use test_program_methods::MODIFIED_TRANSFER_ELF; + // This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of + // `program_methods` + Self::new(MODIFIED_TRANSFER_ELF.to_vec()).unwrap() + } } #[test] diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 72efbd2..86df3a5 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -154,6 +154,12 @@ impl V02State { *current_account = post; } + // 5. Increment nonces for public signers + for account_id in tx.signer_account_ids() { + let current_account = self.get_account_by_id_mut(account_id); + current_account.nonce += 1; + } + Ok(()) } @@ -272,7 +278,10 @@ pub mod tests { error::NssaError, execute_and_prove, privacy_preserving_transaction::{ - PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet, + PrivacyPreservingTransaction, + circuit::{self, ProgramWithDependencies}, + message::Message, + witness_set::WitnessSet, }, program::Program, public_transaction, @@ -859,7 +868,7 @@ pub mod tests { &[0xdeadbeef], &[(recipient_keys.npk(), shared_secret)], &[], - &Program::authenticated_transfer_program(), + &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -911,7 +920,7 @@ pub mod tests { sender_keys.nsk, state.get_proof_for_commitment(&sender_commitment).unwrap(), )], - &program, + &program.into(), ) .unwrap(); @@ -963,7 +972,7 @@ pub mod tests { sender_keys.nsk, state.get_proof_for_commitment(&sender_commitment).unwrap(), )], - &program, + &program.into(), ) .unwrap(); @@ -1176,7 +1185,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1202,7 +1211,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1228,7 +1237,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1254,7 +1263,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1282,7 +1291,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.to_owned().into(), ); assert!(matches!(result, Err(NssaError::ProgramProveFailed(_)))); @@ -1308,7 +1317,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1343,7 +1352,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1369,7 +1378,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1404,7 +1413,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1441,7 +1450,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1482,7 +1491,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1516,7 +1525,7 @@ pub mod tests { &[0xdeadbeef1, 0xdeadbeef2], &private_account_keys, &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1557,7 +1566,7 @@ pub mod tests { ), ], &private_account_auth, - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1605,7 +1614,7 @@ pub mod tests { &[0xdeadbeef1, 0xdeadbeef2], &private_account_keys, &private_account_auth, - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1651,7 +1660,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1698,7 +1707,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1744,7 +1753,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1790,7 +1799,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1834,7 +1843,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1863,7 +1872,7 @@ pub mod tests { &[], &[], &[], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1905,7 +1914,7 @@ pub mod tests { ), ], &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1951,7 +1960,7 @@ pub mod tests { &[0xdeadbeef1, 0xdeadbeef2], &private_account_keys, &[(sender_keys.nsk, (0, vec![]))], - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -1997,7 +2006,7 @@ pub mod tests { ), ], &private_account_auth, - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -2088,7 +2097,7 @@ pub mod tests { (sender_keys.npk(), shared_secret), ], &private_account_auth, - &program, + &program.into(), ); assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); @@ -2131,7 +2140,7 @@ pub mod tests { } #[test] - fn test_chained_call_succeeds() { + fn test_public_chained_call() { let program = Program::chain_caller(); let key = PrivateKey::try_new([1; 32]).unwrap(); let from = AccountId::from(&PublicKey::new_from_private_key(&key)); @@ -2310,6 +2319,128 @@ pub mod tests { assert_eq!(to_post, expected_to_post); } + #[test] + fn test_private_chained_call() { + // Arrange + let chain_caller = Program::chain_caller(); + let auth_transfers = Program::authenticated_transfer_program(); + let from_keys = test_private_account_keys_1(); + let to_keys = test_private_account_keys_2(); + let initial_balance = 100; + let from_account = AccountWithMetadata::new( + Account { + program_owner: auth_transfers.id(), + balance: initial_balance, + ..Account::default() + }, + true, + &from_keys.npk(), + ); + let to_account = AccountWithMetadata::new( + Account { + program_owner: auth_transfers.id(), + ..Account::default() + }, + true, + &to_keys.npk(), + ); + + let from_commitment = Commitment::new(&from_keys.npk(), &from_account.account); + let to_commitment = Commitment::new(&to_keys.npk(), &to_account.account); + let mut state = V02State::new_with_genesis_accounts( + &[], + &[from_commitment.clone(), to_commitment.clone()], + ) + .with_test_programs(); + let amount: u128 = 37; + let instruction: (u128, ProgramId, u32, Option) = ( + amount, + Program::authenticated_transfer_program().id(), + 1, + None, + ); + + let from_esk = [3; 32]; + let from_ss = SharedSecretKey::new(&from_esk, &from_keys.ivk()); + let from_epk = EphemeralPublicKey::from_scalar(from_esk); + + let to_esk = [3; 32]; + let to_ss = SharedSecretKey::new(&to_esk, &to_keys.ivk()); + let to_epk = EphemeralPublicKey::from_scalar(to_esk); + + let mut dependencies = HashMap::new(); + + dependencies.insert(auth_transfers.id(), auth_transfers); + let program_with_deps = ProgramWithDependencies::new(chain_caller, dependencies); + + let from_new_nonce = 0xdeadbeef1; + let to_new_nonce = 0xdeadbeef2; + + let from_expected_post = Account { + balance: initial_balance - amount, + nonce: from_new_nonce, + ..from_account.account.clone() + }; + let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post); + + let to_expected_post = Account { + balance: amount, + nonce: to_new_nonce, + ..to_account.account.clone() + }; + let to_expected_commitment = Commitment::new(&to_keys.npk(), &to_expected_post); + + // Act + let (output, proof) = execute_and_prove( + &[to_account, from_account], + &Program::serialize_instruction(instruction).unwrap(), + &[1, 1], + &[from_new_nonce, to_new_nonce], + &[(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)], + &[ + ( + from_keys.nsk, + state.get_proof_for_commitment(&from_commitment).unwrap(), + ), + ( + to_keys.nsk, + state.get_proof_for_commitment(&to_commitment).unwrap(), + ), + ], + &program_with_deps, + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + (to_keys.npk(), to_keys.ivk(), to_epk), + (from_keys.npk(), from_keys.ivk(), from_epk), + ], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let transaction = PrivacyPreservingTransaction::new(message, witness_set); + + state + .transition_from_privacy_preserving_transaction(&transaction) + .unwrap(); + + // Assert + assert!( + state + .get_proof_for_commitment(&from_expected_commitment) + .is_some() + ); + assert!( + state + .get_proof_for_commitment(&to_expected_commitment) + .is_some() + ); + } + #[test] fn test_pda_mechanism_with_pinata_token_program() { let pinata_token = Program::pinata_token(); diff --git a/nssa/test_program_methods/guest/src/bin/burner.rs b/nssa/test_program_methods/guest/src/bin/burner.rs index 01b46b2..5deef7c 100644 --- a/nssa/test_program_methods/guest/src/bin/burner.rs +++ b/nssa/test_program_methods/guest/src/bin/burner.rs @@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write type Instruction = u128; fn main() { - let ProgramInput { - pre_states, - instruction: balance_to_burn, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: balance_to_burn, + }, + instruction_words, + ) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -17,5 +20,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance -= balance_to_burn; - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); } diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/nssa/test_program_methods/guest/src/bin/chain_caller.rs index ee01ffa..f2d3cb6 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/nssa/test_program_methods/guest/src/bin/chain_caller.rs @@ -10,10 +10,13 @@ type Instruction = (u128, ProgramId, u32, Option); /// It permutes the order of the input accounts on the subsequent call /// The `ProgramId` in the instruction must be the program_id of the authenticated transfers program fn main() { - let ProgramInput { - pre_states, + let ( + ProgramInput { + pre_states, instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed), - } = read_nssa_inputs::(); + }, + instruction_words + ) = read_nssa_inputs::(); let [recipient_pre, sender_pre] = match pre_states.try_into() { Ok(array) => array, @@ -44,6 +47,7 @@ fn main() { } write_nssa_outputs_with_chained_call( + instruction_words, vec![sender_pre.clone(), recipient_pre.clone()], vec![ AccountPostState::new(sender_pre.account), diff --git a/nssa/test_program_methods/guest/src/bin/claimer.rs b/nssa/test_program_methods/guest/src/bin/claimer.rs index 7687e5a..8687704 100644 --- a/nssa/test_program_methods/guest/src/bin/claimer.rs +++ b/nssa/test_program_methods/guest/src/bin/claimer.rs @@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write type Instruction = (); fn main() { - let ProgramInput { - pre_states, - instruction: _, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: _, + }, + instruction_words, + ) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -15,5 +18,5 @@ fn main() { let account_post = AccountPostState::new_claimed(pre.account.clone()); - write_nssa_outputs(vec![pre], vec![account_post]); + write_nssa_outputs(instruction_words, vec![pre], vec![account_post]); } diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/nssa/test_program_methods/guest/src/bin/data_changer.rs index b590886..9154440 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/data_changer.rs @@ -4,7 +4,7 @@ type Instruction = Vec; /// A program that modifies the account data by setting bytes sent in instruction. fn main() { - let ProgramInput { pre_states, instruction: data } = read_nssa_inputs::(); + let (ProgramInput { pre_states, instruction: data }, instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -15,5 +15,9 @@ fn main() { let mut account_post = account_pre.clone(); account_post.data = data.try_into().expect("provided data should fit into data limit"); - write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]); + write_nssa_outputs( + instruction_words, + vec![pre], + vec![AccountPostState::new_claimed(account_post)], + ); } diff --git a/nssa/test_program_methods/guest/src/bin/extra_output.rs b/nssa/test_program_methods/guest/src/bin/extra_output.rs index 7137262..4950f14 100644 --- a/nssa/test_program_methods/guest/src/bin/extra_output.rs +++ b/nssa/test_program_methods/guest/src/bin/extra_output.rs @@ -6,7 +6,7 @@ use nssa_core::{ type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -16,6 +16,7 @@ fn main() { let account_pre = pre.account.clone(); write_nssa_outputs( + instruction_words, vec![pre], vec![ AccountPostState::new(account_pre), diff --git a/nssa/test_program_methods/guest/src/bin/minter.rs b/nssa/test_program_methods/guest/src/bin/minter.rs index 5f69772..51baa5e 100644 --- a/nssa/test_program_methods/guest/src/bin/minter.rs +++ b/nssa/test_program_methods/guest/src/bin/minter.rs @@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance += 1; - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); } diff --git a/nssa/test_program_methods/guest/src/bin/missing_output.rs b/nssa/test_program_methods/guest/src/bin/missing_output.rs index f7d78be..7b910c6 100644 --- a/nssa/test_program_methods/guest/src/bin/missing_output.rs +++ b/nssa/test_program_methods/guest/src/bin/missing_output.rs @@ -3,7 +3,7 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); let [pre1, pre2] = match pre_states.try_into() { Ok(array) => array, @@ -12,5 +12,9 @@ fn main() { let account_pre1 = pre1.account.clone(); - write_nssa_outputs(vec![pre1, pre2], vec![AccountPostState::new(account_pre1)]); + write_nssa_outputs( + instruction_words, + vec![pre1, pre2], + vec![AccountPostState::new(account_pre1)], + ); } diff --git a/nssa/program_methods/guest/src/bin/modified_transfer.rs b/nssa/test_program_methods/guest/src/bin/modified_transfer.rs similarity index 63% rename from nssa/program_methods/guest/src/bin/modified_transfer.rs rename to nssa/test_program_methods/guest/src/bin/modified_transfer.rs index 0f85e53..dd93e83 100644 --- a/nssa/program_methods/guest/src/bin/modified_transfer.rs +++ b/nssa/test_program_methods/guest/src/bin/modified_transfer.rs @@ -5,32 +5,32 @@ use nssa_core::{ /// Initializes a default account under the ownership of this program. /// This is achieved by a noop. -fn initialize_account(pre_state: AccountWithMetadata) { +fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState { let account_to_claim = pre_state.account.clone(); let is_authorized = pre_state.is_authorized; // Continue only if the account to claim has default values if account_to_claim != Account::default() { - return; + panic!("Account is already initialized"); } // Continue only if the owner authorized this operation if !is_authorized { - return; + panic!("Missing required authorization"); } - // Noop will result in account being claimed for this program - write_nssa_outputs( - vec![pre_state], - vec![AccountPostState::new(account_to_claim)], - ); + AccountPostState::new(account_to_claim) } /// Transfers `balance_to_move` native balance from `sender` to `recipient`. -fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) { +fn transfer( + sender: AccountWithMetadata, + recipient: AccountWithMetadata, + balance_to_move: u128, +) -> Vec { // Continue only if the sender has authorized this operation if !sender.is_authorized { - return; + panic!("Missing required authorization"); } // This segment is a safe protection from authenticated transfer program @@ -50,29 +50,33 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance sender_post.balance -= balance_to_move + malicious_offset; recipient_post.balance += balance_to_move + malicious_offset; - write_nssa_outputs( - vec![sender, recipient], - vec![ - AccountPostState::new(sender_post), - AccountPostState::new(recipient_post), - ], - ); + vec![ + AccountPostState::new(sender_post), + AccountPostState::new(recipient_post), + ] } /// A transfer of balance program. /// To be used both in public and private contexts. fn main() { // Read input accounts. - let ProgramInput { - pre_states, - instruction: balance_to_move, - } = read_nssa_inputs(); + let ( + ProgramInput { + pre_states, + instruction: balance_to_move, + }, + instruction_data, + ) = read_nssa_inputs(); - match (pre_states.as_slice(), balance_to_move) { - ([account_to_claim], 0) => initialize_account(account_to_claim.clone()), + let post_states = match (pre_states.as_slice(), balance_to_move) { + ([account_to_claim], 0) => { + let post = initialize_account(account_to_claim.clone()); + vec![post] + } ([sender, recipient], balance_to_move) => { transfer(sender.clone(), recipient.clone(), balance_to_move) } _ => panic!("invalid params"), - } + }; + write_nssa_outputs(instruction_data, pre_states, post_states); } diff --git a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs index fc24572..4ca6c73 100644 --- a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs @@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. } , instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.nonce += 1; - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(instruction_words ,vec![pre], vec![AccountPostState::new(account_post)]); } diff --git a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs index 2fa5400..2b212c1 100644 --- a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs @@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, type Instruction = (); fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7]; - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); } diff --git a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs index be56e16..e1dbc1b 100644 --- a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs +++ b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs @@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write type Instruction = u128; fn main() { - let ProgramInput { - pre_states, - instruction: balance, - } = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: balance, + }, + instruction_words, + ) = read_nssa_inputs::(); let [sender_pre, receiver_pre] = match pre_states.try_into() { Ok(array) => array, @@ -19,6 +22,7 @@ fn main() { receiver_post.balance += balance; write_nssa_outputs( + instruction_words, vec![sender_pre, receiver_pre], vec![ AccountPostState::new(sender_post), diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 6f97c63..c93b357 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -23,6 +23,7 @@ itertools.workspace = true sha2.workspace = true futures.workspace = true async-stream = "0.3.6" +indicatif = { version = "0.18.3", features = ["improved_unicode"] } [dependencies.key_protocol] path = "../key_protocol" diff --git a/wallet/src/chain_storage.rs b/wallet/src/chain_storage.rs index 1223d1f..7d8ed50 100644 --- a/wallet/src/chain_storage.rs +++ b/wallet/src/chain_storage.rs @@ -8,6 +8,7 @@ use key_protocol::{ }, key_protocol_core::NSSAUserData, }; +use log::debug; use nssa::program::Program; use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig}; @@ -127,7 +128,7 @@ impl WalletChainStore { account_id: nssa::AccountId, account: nssa_core::account::Account, ) { - println!("inserting at address {account_id}, this account {account:?}"); + debug!("inserting at address {account_id}, this account {account:?}"); let entry = self .user_data diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 2299f9e..480480d 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -96,13 +96,13 @@ pub enum NewSubcommand { Public { #[arg(long)] /// Chain index of a parent node - cci: ChainIndex, + cci: Option, }, /// Register new private account Private { #[arg(long)] /// Chain index of a parent node - cci: ChainIndex, + cci: Option, }, } @@ -113,9 +113,11 @@ impl WalletSubcommand for NewSubcommand { ) -> Result { match self { NewSubcommand::Public { cci } => { - let account_id = wallet_core.create_new_account_public(cci); + let (account_id, chain_index) = wallet_core.create_new_account_public(cci); - println!("Generated new account with account_id Public/{account_id}"); + println!( + "Generated new account with account_id Public/{account_id} at path {chain_index}" + ); let path = wallet_core.store_persistent_data().await?; @@ -124,7 +126,7 @@ impl WalletSubcommand for NewSubcommand { Ok(SubcommandReturnValue::RegisterAccount { account_id }) } NewSubcommand::Private { cci } => { - let account_id = wallet_core.create_new_account_private(cci); + let (account_id, chain_index) = wallet_core.create_new_account_private(cci); let (key, _) = wallet_core .storage @@ -133,7 +135,7 @@ impl WalletSubcommand for NewSubcommand { .unwrap(); println!( - "Generated new account with account_id Private/{}", + "Generated new account with account_id Private/{} at path {chain_index}", account_id.to_bytes().to_base58() ); println!("With npk {}", hex::encode(key.nullifer_public_key.0)); diff --git a/wallet/src/cli/config.rs b/wallet/src/cli/config.rs index d4b3f5a..bfebf9b 100644 --- a/wallet/src/cli/config.rs +++ b/wallet/src/cli/config.rs @@ -9,10 +9,6 @@ use crate::{ /// Represents generic config CLI subcommand #[derive(Subcommand, Debug, Clone)] pub enum ConfigSubcommand { - /// Command to explicitly setup config and storage - /// - /// Does nothing in case if both already present - Setup {}, /// Getter of config fields Get { key: String }, /// Setter of config fields @@ -27,11 +23,6 @@ impl WalletSubcommand for ConfigSubcommand { wallet_core: &mut WalletCore, ) -> Result { match self { - ConfigSubcommand::Setup {} => { - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); - } ConfigSubcommand::Get { key } => match key.as_str() { "all" => { let config_str = diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index b3d40b8..61b8697 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -1,6 +1,8 @@ -use anyhow::Result; +use std::{io::Write, path::PathBuf}; + +use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; -use nssa::program::Program; +use nssa::{ProgramDeploymentTransaction, program::Program}; use crate::{ WalletCore, @@ -13,7 +15,7 @@ use crate::{ token::TokenProgramAgnosticSubcommand, }, }, - helperfunctions::{fetch_config, merge_auth_config}, + helperfunctions::{fetch_config, fetch_persistent_storage, merge_auth_config}, }; pub mod account; @@ -51,20 +53,16 @@ pub enum Command { /// Command to setup config, get and set config fields #[command(subcommand)] Config(ConfigSubcommand), -} - -/// Represents overarching CLI command for a wallet with setup included -#[derive(Debug, Subcommand, Clone)] -#[clap(about)] -pub enum OverCommand { - /// Represents CLI command for a wallet - #[command(subcommand)] - Command(Command), - /// Setup of a storage. Initializes rots for public and private trees from `password`. - Setup { + /// Restoring keys from given password at given `depth` + /// + /// !!!WARNING!!! will rewrite current storage + RestoreKeys { #[arg(short, long)] - password: String, + /// Indicates, how deep in tree accounts may be. Affects command complexity. + depth: u32, }, + /// Deploy a program + DeployProgram { binary_filepath: PathBuf }, } /// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -84,7 +82,7 @@ pub struct Args { pub auth: Option, /// Wallet command #[command(subcommand)] - pub command: Option, + pub command: Option, } #[derive(Debug, Clone)] @@ -104,8 +102,15 @@ pub async fn execute_subcommand_with_auth( command: Command, auth: Option, ) -> Result { + if fetch_persistent_storage().await.is_err() { + println!("Persistent storage not found, need to execute setup"); + + let password = read_password_from_stdin()?; + execute_setup_with_auth(password, auth.clone()).await?; + } + let wallet_config = fetch_config().await?; - let wallet_config = merge_auth_config(wallet_config, auth)?; + let wallet_config = merge_auth_config(wallet_config, auth.clone())?; let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; let subcommand_ret = match command { @@ -165,6 +170,27 @@ pub async fn execute_subcommand_with_auth( .handle_subcommand(&mut wallet_core) .await? } + Command::RestoreKeys { depth } => { + let password = read_password_from_stdin()?; + execute_keys_restoration_with_auth(password, depth, auth).await?; + + SubcommandReturnValue::Empty + } + Command::DeployProgram { binary_filepath } => { + let bytecode: Vec = std::fs::read(&binary_filepath).context(format!( + "Failed to read program binary at {}", + binary_filepath.display() + ))?; + let message = nssa::program_deployment_transaction::Message::new(bytecode); + let transaction = ProgramDeploymentTransaction::new(message); + let _response = wallet_core + .sequencer_client + .send_tx_program(transaction) + .await + .context("Transaction submission error"); + + SubcommandReturnValue::Empty + } }; Ok(subcommand_ret) @@ -193,6 +219,16 @@ pub async fn execute_continuous_run_with_auth(auth: Option) -> Result<() } } +pub fn read_password_from_stdin() -> Result { + let mut password = String::new(); + + print!("Input password: "); + std::io::stdout().flush()?; + std::io::stdin().read_line(&mut password)?; + + Ok(password.trim().to_string()) +} + pub async fn execute_setup(password: String) -> Result<()> { execute_setup_with_auth(password, None).await } @@ -206,3 +242,67 @@ pub async fn execute_setup_with_auth(password: String, auth: Option) -> Ok(()) } + +pub async fn execute_keys_restoration(password: String, depth: u32) -> Result<()> { + execute_keys_restoration_with_auth(password, depth, None).await +} + +pub async fn execute_keys_restoration_with_auth( + password: String, + depth: u32, + auth: Option, +) -> Result<()> { + let config = fetch_config().await?; + let config = merge_auth_config(config, auth)?; + let mut wallet_core = + WalletCore::start_from_config_new_storage(config.clone(), password.clone()).await?; + + wallet_core + .storage + .user_data + .public_key_tree + .generate_tree_for_depth(depth); + + println!("Public tree generated"); + + wallet_core + .storage + .user_data + .private_key_tree + .generate_tree_for_depth(depth); + + println!("Private tree generated"); + + wallet_core + .storage + .user_data + .public_key_tree + .cleanup_tree_remove_uninit_layered(depth, wallet_core.sequencer_client.clone()) + .await?; + + println!("Public tree cleaned up"); + + let last_block = wallet_core + .sequencer_client + .get_last_block() + .await? + .last_block; + + println!("Last block is {last_block}"); + + wallet_core.sync_to_block(last_block).await?; + + println!("Private tree clean up start"); + + wallet_core + .storage + .user_data + .private_key_tree + .cleanup_tree_remove_uninit_layered(depth); + + println!("Private tree cleaned up"); + + wallet_core.store_persistent_data().await?; + + Ok(()) +} diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index d1a27dd..4480a1e 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -14,8 +14,6 @@ use crate::{ #[derive(Subcommand, Debug, Clone)] pub enum TokenProgramAgnosticSubcommand { /// Produce a new token - /// - /// Currently the only supported privacy options is for public definition New { /// definition_account_id - valid 32 byte base58 string with privacy prefix #[arg(long)] @@ -72,8 +70,8 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand { let underlying_subcommand = match (definition_addr_privacy, supply_addr_privacy) { (AccountPrivacyKind::Public, AccountPrivacyKind::Public) => { - TokenProgramSubcommand::Public( - TokenProgramSubcommandPublic::CreateNewToken { + TokenProgramSubcommand::Create( + CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp { definition_account_id, supply_account_id, name, @@ -82,8 +80,8 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand { ) } (AccountPrivacyKind::Public, AccountPrivacyKind::Private) => { - TokenProgramSubcommand::Private( - TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned { + TokenProgramSubcommand::Create( + CreateNewTokenProgramSubcommand::NewPublicDefPrivateSupp { definition_account_id, supply_account_id, name, @@ -92,14 +90,24 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand { ) } (AccountPrivacyKind::Private, AccountPrivacyKind::Private) => { - // ToDo: maybe implement this one. It is not immediately clear why - // definition should be private. - anyhow::bail!("Unavailable privacy pairing") + TokenProgramSubcommand::Create( + CreateNewTokenProgramSubcommand::NewPrivateDefPrivateSupp { + definition_account_id, + supply_account_id, + name, + total_supply, + }, + ) } (AccountPrivacyKind::Private, AccountPrivacyKind::Public) => { - // ToDo: Probably valid. If definition is not public, but supply is it is - // very suspicious. - anyhow::bail!("Unavailable privacy pairing") + TokenProgramSubcommand::Create( + CreateNewTokenProgramSubcommand::NewPrivateDefPublicSupp { + definition_account_id, + supply_account_id, + name, + total_supply, + }, + ) } }; @@ -202,6 +210,9 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand { /// Represents generic CLI subcommand for a wallet working with token_program #[derive(Subcommand, Debug, Clone)] pub enum TokenProgramSubcommand { + /// Creation of new token + #[command(subcommand)] + Create(CreateNewTokenProgramSubcommand), /// Public execution #[command(subcommand)] Public(TokenProgramSubcommandPublic), @@ -219,17 +230,6 @@ pub enum TokenProgramSubcommand { /// Represents generic public CLI subcommand for a wallet working with token_program #[derive(Subcommand, Debug, Clone)] pub enum TokenProgramSubcommandPublic { - // Create a new token using the token program - CreateNewToken { - #[arg(short, long)] - definition_account_id: String, - #[arg(short, long)] - supply_account_id: String, - #[arg(short, long)] - name: String, - #[arg(short, long)] - total_supply: u128, - }, // Transfer tokens using the token program TransferToken { #[arg(short, long)] @@ -244,17 +244,6 @@ pub enum TokenProgramSubcommandPublic { /// Represents generic private CLI subcommand for a wallet working with token_program #[derive(Subcommand, Debug, Clone)] pub enum TokenProgramSubcommandPrivate { - // Create a new token using the token program - CreateNewTokenPrivateOwned { - #[arg(short, long)] - definition_account_id: String, - #[arg(short, long)] - supply_account_id: String, - #[arg(short, long)] - name: String, - #[arg(short, long)] - total_supply: u128, - }, // Transfer tokens using the token program TransferTokenPrivateOwned { #[arg(short, long)] @@ -320,35 +309,69 @@ pub enum TokenProgramSubcommandShielded { }, } +/// Represents generic initialization subcommand for a wallet working with token_program +#[derive(Subcommand, Debug, Clone)] +pub enum CreateNewTokenProgramSubcommand { + /// Create a new token using the token program + /// + /// Definition - public, supply - public + NewPublicDefPublicSupp { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + supply_account_id: String, + #[arg(short, long)] + name: String, + #[arg(short, long)] + total_supply: u128, + }, + /// Create a new token using the token program + /// + /// Definition - public, supply - private + NewPublicDefPrivateSupp { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + supply_account_id: String, + #[arg(short, long)] + name: String, + #[arg(short, long)] + total_supply: u128, + }, + /// Create a new token using the token program + /// + /// Definition - private, supply - public + NewPrivateDefPublicSupp { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + supply_account_id: String, + #[arg(short, long)] + name: String, + #[arg(short, long)] + total_supply: u128, + }, + /// Create a new token using the token program + /// + /// Definition - private, supply - private + NewPrivateDefPrivateSupp { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + supply_account_id: String, + #[arg(short, long)] + name: String, + #[arg(short, long)] + total_supply: u128, + }, +} + impl WalletSubcommand for TokenProgramSubcommandPublic { async fn handle_subcommand( self, wallet_core: &mut WalletCore, ) -> Result { match self { - TokenProgramSubcommandPublic::CreateNewToken { - definition_account_id, - supply_account_id, - name, - total_supply, - } => { - let name = name.as_bytes(); - if name.len() > 6 { - // TODO: return error - panic!(); - } - let mut name_bytes = [0; 6]; - name_bytes[..name.len()].copy_from_slice(name); - Token(wallet_core) - .send_new_definition( - definition_account_id.parse().unwrap(), - supply_account_id.parse().unwrap(), - name_bytes, - total_supply, - ) - .await?; - Ok(SubcommandReturnValue::Empty) - } TokenProgramSubcommandPublic::TransferToken { sender_account_id, recipient_account_id, @@ -373,54 +396,6 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { wallet_core: &mut WalletCore, ) -> Result { match self { - TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned { - definition_account_id, - supply_account_id, - name, - total_supply, - } => { - let name = name.as_bytes(); - if name.len() > 6 { - // TODO: return error - panic!("Name length mismatch"); - } - let mut name_bytes = [0; 6]; - name_bytes[..name.len()].copy_from_slice(name); - - let definition_account_id: AccountId = definition_account_id.parse().unwrap(); - let supply_account_id: AccountId = supply_account_id.parse().unwrap(); - - let (res, secret_supply) = Token(wallet_core) - .send_new_definition_private_owned( - definition_account_id, - supply_account_id, - name_bytes, - total_supply, - ) - .await?; - - println!("Results of tx send are {res:#?}"); - - let tx_hash = res.tx_hash; - let transfer_tx = wallet_core - .poll_native_token_transfer(tx_hash.clone()) - .await?; - - if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_supply, supply_account_id)]; - - wallet_core.decode_insert_privacy_preserving_transaction_results( - tx, - &acc_decode_data, - )?; - } - - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); - - Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) - } TokenProgramSubcommandPrivate::TransferTokenPrivateOwned { sender_account_id, recipient_account_id, @@ -657,12 +632,195 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { } } +impl WalletSubcommand for CreateNewTokenProgramSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + CreateNewTokenProgramSubcommand::NewPrivateDefPrivateSupp { + definition_account_id, + supply_account_id, + name, + total_supply, + } => { + let name = name.as_bytes(); + if name.len() > 6 { + // TODO: return error + panic!("Name length mismatch"); + } + let mut name_bytes = [0; 6]; + name_bytes[..name.len()].copy_from_slice(name); + + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let supply_account_id: AccountId = supply_account_id.parse().unwrap(); + + let (res, [secret_definition, secret_supply]) = Token(wallet_core) + .send_new_definition_private_owned_definiton_and_supply( + definition_account_id, + supply_account_id, + name_bytes, + total_supply, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![ + (secret_definition, definition_account_id), + (secret_supply, supply_account_id), + ]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + CreateNewTokenProgramSubcommand::NewPrivateDefPublicSupp { + definition_account_id, + supply_account_id, + name, + total_supply, + } => { + let name = name.as_bytes(); + if name.len() > 6 { + // TODO: return error + panic!("Name length mismatch"); + } + let mut name_bytes = [0; 6]; + name_bytes[..name.len()].copy_from_slice(name); + + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let supply_account_id: AccountId = supply_account_id.parse().unwrap(); + + let (res, secret_definition) = Token(wallet_core) + .send_new_definition_private_owned_definiton( + definition_account_id, + supply_account_id, + name_bytes, + total_supply, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![(secret_definition, definition_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + CreateNewTokenProgramSubcommand::NewPublicDefPrivateSupp { + definition_account_id, + supply_account_id, + name, + total_supply, + } => { + let name = name.as_bytes(); + if name.len() > 6 { + // TODO: return error + panic!("Name length mismatch"); + } + let mut name_bytes = [0; 6]; + name_bytes[..name.len()].copy_from_slice(name); + + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let supply_account_id: AccountId = supply_account_id.parse().unwrap(); + + let (res, secret_supply) = Token(wallet_core) + .send_new_definition_private_owned_supply( + definition_account_id, + supply_account_id, + name_bytes, + total_supply, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![(secret_supply, supply_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp { + definition_account_id, + supply_account_id, + name, + total_supply, + } => { + let name = name.as_bytes(); + if name.len() > 6 { + // TODO: return error + panic!(); + } + let mut name_bytes = [0; 6]; + name_bytes[..name.len()].copy_from_slice(name); + Token(wallet_core) + .send_new_definition( + definition_account_id.parse().unwrap(), + supply_account_id.parse().unwrap(), + name_bytes, + total_supply, + ) + .await?; + Ok(SubcommandReturnValue::Empty) + } + } + } +} + impl WalletSubcommand for TokenProgramSubcommand { async fn handle_subcommand( self, wallet_core: &mut WalletCore, ) -> Result { match self { + TokenProgramSubcommand::Create(creation_subcommand) => { + creation_subcommand.handle_subcommand(wallet_core).await + } TokenProgramSubcommand::Private(private_subcommand) => { private_subcommand.handle_subcommand(wallet_core).await } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 11f4a4a..bc28311 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -126,13 +126,19 @@ impl WalletCore { Ok(config_path) } - pub fn create_new_account_public(&mut self, chain_index: ChainIndex) -> AccountId { + pub fn create_new_account_public( + &mut self, + chain_index: Option, + ) -> (AccountId, ChainIndex) { self.storage .user_data .generate_new_public_transaction_private_key(chain_index) } - pub fn create_new_account_private(&mut self, chain_index: ChainIndex) -> AccountId { + pub fn create_new_account_private( + &mut self, + chain_index: Option, + ) -> (AccountId, ChainIndex) { self.storage .user_data .generate_new_privacy_preserving_transaction_key_chain(chain_index) @@ -277,7 +283,7 @@ impl WalletCore { .map(|keys| (keys.npk.clone(), keys.ssk.clone())) .collect::>(), &acc_manager.private_account_auth(), - program, + &program.to_owned().into(), ) .unwrap(); @@ -320,11 +326,14 @@ impl WalletCore { } let before_polling = std::time::Instant::now(); + let num_of_blocks = block_id - self.last_synced_block; + println!("Syncing to block {block_id}. Blocks to sync: {num_of_blocks}"); let poller = self.poller.clone(); let mut blocks = std::pin::pin!(poller.poll_block_range(self.last_synced_block + 1..=block_id)); + let bar = indicatif::ProgressBar::new(num_of_blocks); while let Some(block) = blocks.try_next().await? { for tx in block.transactions { let nssa_tx = NSSATransaction::try_from(&tx)?; @@ -333,7 +342,9 @@ impl WalletCore { self.last_synced_block = block.block_id; self.store_persistent_data().await?; + bar.inc(1); } + bar.finish(); println!( "Synced to block {block_id} in {:?}", @@ -393,7 +404,7 @@ impl WalletCore { .collect::>(); for (affected_account_id, new_acc) in affected_accounts { - println!( + info!( "Received new account for account_id {affected_account_id:#?} with account object {new_acc:#?}" ); self.storage diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 27d102d..708b1fc 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,10 +1,7 @@ use anyhow::Result; use clap::{CommandFactory as _, Parser as _}; use tokio::runtime::Builder; -use wallet::cli::{ - Args, OverCommand, execute_continuous_run_with_auth, execute_setup_with_auth, - execute_subcommand_with_auth, -}; +use wallet::cli::{Args, execute_continuous_run_with_auth, execute_subcommand_with_auth}; pub const NUM_THREADS: usize = 2; @@ -25,16 +22,9 @@ fn main() -> Result<()> { env_logger::init(); runtime.block_on(async move { - if let Some(over_command) = args.command { - match over_command { - OverCommand::Command(command) => { - let _output = execute_subcommand_with_auth(command, args.auth).await?; - Ok(()) - } - OverCommand::Setup { password } => { - execute_setup_with_auth(password, args.auth).await - } - } + if let Some(command) = args.command { + let _output = execute_subcommand_with_auth(command, args.auth).await?; + Ok(()) } else if args.continuous_run { execute_continuous_run_with_auth(args.auth).await } else { diff --git a/wallet/src/pinata_interactions.rs b/wallet/src/pinata_interactions.rs new file mode 100644 index 0000000..65a67b7 --- /dev/null +++ b/wallet/src/pinata_interactions.rs @@ -0,0 +1,161 @@ +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use nssa::{AccountId, privacy_preserving_transaction::circuit}; +use nssa_core::{MembershipProof, SharedSecretKey, account::AccountWithMetadata}; + +use crate::{ + WalletCore, helperfunctions::produce_random_nonces, transaction_utils::AccountPreparedData, +}; + +impl WalletCore { + pub async fn claim_pinata( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + ) -> Result { + let account_ids = vec![pinata_account_id, winner_account_id]; + let program_id = nssa::program::Program::pinata().id(); + let message = + nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn claim_pinata_private_owned_account_already_initialized( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + winner_proof: MembershipProof, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: winner_nsk, + npk: winner_npk, + ipk: winner_ipk, + auth_acc: winner_pre, + proof: _, + } = self + .private_acc_preparation(winner_account_id, true, false) + .await?; + + let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap(); + + let program = nssa::program::Program::pinata(); + + let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id); + + let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk); + let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[pinata_pre, winner_pre], + &nssa::program::Program::serialize_instruction(solution).unwrap(), + &[0, 1], + &produce_random_nonces(1), + &[(winner_npk.clone(), shared_secret_winner.clone())], + &[(winner_nsk.unwrap(), winner_proof)], + &program.into(), + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![pinata_account_id], + vec![], + vec![( + winner_npk.clone(), + winner_ipk.clone(), + eph_holder_winner.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( + message, + witness_set, + ); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_winner], + )) + } + + pub async fn claim_pinata_private_owned_account_not_initialized( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: _, + npk: winner_npk, + ipk: winner_ipk, + auth_acc: winner_pre, + proof: _, + } = self + .private_acc_preparation(winner_account_id, false, false) + .await?; + + let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap(); + + let program = nssa::program::Program::pinata(); + + let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id); + + let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk); + let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[pinata_pre, winner_pre], + &nssa::program::Program::serialize_instruction(solution).unwrap(), + &[0, 2], + &produce_random_nonces(1), + &[(winner_npk.clone(), shared_secret_winner.clone())], + &[], + &program.into(), + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![pinata_account_id], + vec![], + vec![( + winner_npk.clone(), + winner_ipk.clone(), + eph_holder_winner.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( + message, + witness_set, + ); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_winner], + )) + } +} diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index 7c97155..4ec9c12 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -38,7 +38,7 @@ impl Token<'_> { Ok(self.0.sequencer_client.send_tx_public(tx).await?) } - pub async fn send_new_definition_private_owned( + pub async fn send_new_definition_private_owned_supply( &self, definition_account_id: AccountId, supply_account_id: AccountId, @@ -61,11 +61,66 @@ impl Token<'_> { let first = secrets .into_iter() .next() - .expect("expected recipient's secret"); + .expect("expected supply's secret"); (resp, first) }) } + pub async fn send_new_definition_private_owned_definiton( + &self, + definition_account_id: AccountId, + supply_account_id: AccountId, + name: [u8; 6], + total_supply: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let (instruction_data, program) = token_program_preparation_definition(name, total_supply); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::Public(supply_account_id), + ], + &instruction_data, + &program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected definition's secret"); + (resp, first) + }) + } + + pub async fn send_new_definition_private_owned_definiton_and_supply( + &self, + definition_account_id: AccountId, + supply_account_id: AccountId, + name: [u8; 6], + total_supply: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let (instruction_data, program) = token_program_preparation_definition(name, total_supply); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(supply_account_id), + ], + &instruction_data, + &program, + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected definition's secret"); + let second = iter.next().expect("expected supply's secret"); + (resp, [first, second]) + }) + } + pub async fn send_transfer_transaction( &self, sender_account_id: AccountId, diff --git a/wallet/src/transaction_utils.rs b/wallet/src/transaction_utils.rs new file mode 100644 index 0000000..123e49d --- /dev/null +++ b/wallet/src/transaction_utils.rs @@ -0,0 +1,592 @@ +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use nssa::{ + Account, AccountId, PrivacyPreservingTransaction, + privacy_preserving_transaction::{circuit, message::Message, witness_set::WitnessSet}, + program::Program, +}; +use nssa_core::{ + Commitment, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + account::AccountWithMetadata, encryption::IncomingViewingPublicKey, program::InstructionData, +}; + +use crate::{WalletCore, helperfunctions::produce_random_nonces}; + +pub(crate) struct AccountPreparedData { + pub nsk: Option, + pub npk: NullifierPublicKey, + pub ipk: IncomingViewingPublicKey, + pub auth_acc: AccountWithMetadata, + pub proof: Option, +} + +impl WalletCore { + pub(crate) async fn private_acc_preparation( + &self, + account_id: AccountId, + is_authorized: bool, + needs_proof: bool, + ) -> Result { + let Some((from_keys, from_acc)) = self + .storage + .user_data + .get_private_account(&account_id) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let mut nsk = None; + let mut proof = None; + + let from_npk = from_keys.nullifer_public_key; + let from_ipk = from_keys.incoming_viewing_public_key; + + let sender_commitment = Commitment::new(&from_npk, &from_acc); + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), is_authorized, &from_npk); + + if is_authorized { + nsk = Some(from_keys.private_key_holder.nullifier_secret_key); + } + + if needs_proof { + proof = self + .sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap(); + } + + Ok(AccountPreparedData { + nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof, + }) + } + + pub(crate) async fn private_tx_two_accs_all_init( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + to_proof: MembershipProof, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let AccountPreparedData { + nsk: to_nsk, + npk: to_npk, + ipk: to_ipk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, true, false).await?; + + tx_pre_check(&sender_pre.account, &recipient_pre.account)?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); + + let eph_holder_to = EphemeralKeyHolder::new(&to_npk); + let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 1], + &produce_random_nonces(2), + &[ + (from_npk.clone(), shared_secret_from.clone()), + (to_npk.clone(), shared_secret_to.clone()), + ], + &[ + (from_nsk.unwrap(), from_proof.unwrap()), + (to_nsk.unwrap(), to_proof), + ], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk.clone(), + from_ipk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + ), + ( + to_npk.clone(), + to_ipk.clone(), + eph_holder_to.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn private_tx_two_accs_receiver_uninit( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let AccountPreparedData { + nsk: _, + npk: to_npk, + ipk: to_ipk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, false, false).await?; + + tx_pre_check(&sender_pre.account, &recipient_pre.account)?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); + + let eph_holder_to = EphemeralKeyHolder::new(&to_npk); + let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 2], + &produce_random_nonces(2), + &[ + (from_npk.clone(), shared_secret_from.clone()), + (to_npk.clone(), shared_secret_to.clone()), + ], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk.clone(), + from_ipk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + ), + ( + to_npk.clone(), + to_ipk.clone(), + eph_holder_to.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn private_tx_two_accs_receiver_outer( + &self, + from: AccountId, + to_npk: NullifierPublicKey, + to_ipk: IncomingViewingPublicKey, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let to_acc = nssa_core::account::Account::default(); + + tx_pre_check(&sender_pre.account, &to_acc)?; + + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + + let shared_secret_from = eph_holder.calculate_shared_secret_sender(&from_ipk); + let shared_secret_to = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 2], + &produce_random_nonces(2), + &[ + (from_npk.clone(), shared_secret_from.clone()), + (to_npk.clone(), shared_secret_to.clone()), + ], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk.clone(), + from_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + ), + ( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn deshielded_tx_two_accs( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let Ok(to_acc) = self.get_account_public(to).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + tx_pre_check(&sender_pre.account, &to_acc)?; + + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, to); + + let eph_holder = EphemeralKeyHolder::new(&from_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&from_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 0], + &produce_random_nonces(1), + &[(from_npk.clone(), shared_secret.clone())], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![to], + vec![], + vec![( + from_npk.clone(), + from_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_all_init( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + to_proof: MembershipProof, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let AccountPreparedData { + nsk: to_nsk, + npk: to_npk, + ipk: to_ipk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, true, false).await?; + + tx_pre_check(&from_acc, &recipient_pre.account)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 1], + &produce_random_nonces(1), + &[(to_npk.clone(), shared_secret.clone())], + &[(to_nsk.unwrap(), to_proof)], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_receiver_uninit( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let AccountPreparedData { + nsk: _, + npk: to_npk, + ipk: to_ipk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, false, false).await?; + + tx_pre_check(&from_acc, &recipient_pre.account)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 2], + &produce_random_nonces(1), + &[(to_npk.clone(), shared_secret.clone())], + &[], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_receiver_outer( + &self, + from: AccountId, + to_npk: NullifierPublicKey, + to_ipk: IncomingViewingPublicKey, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let to_acc = Account::default(); + + tx_pre_check(&from_acc, &to_acc)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 2], + &produce_random_nonces(1), + &[(to_npk.clone(), shared_secret.clone())], + &[], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk.clone(), + to_ipk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_private(tx).await?) + } + + pub async fn register_account_under_authenticated_transfers_programs_private( + &self, + from: AccountId, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: _, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof: _, + } = self.private_acc_preparation(from, false, false).await?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); + + let instruction: u128 = 0; + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[2], + &produce_random_nonces(1), + &[(from_npk.clone(), shared_secret_from.clone())], + &[], + &Program::authenticated_transfer_program().into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![( + from_npk.clone(), + from_ipk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_from], + )) + } +}