diff --git a/common/src/sequencer_client/mod.rs b/common/src/sequencer_client/mod.rs index fd28c13..b1386c3 100644 --- a/common/src/sequencer_client/mod.rs +++ b/common/src/sequencer_client/mod.rs @@ -13,8 +13,9 @@ use serde_json::Value; use crate::error::{SequencerClientError, SequencerRpcError}; use crate::rpc_primitives::requests::{ GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, - GetProgramIdsRequest, GetProgramIdsResponse, GetProofForCommitmentRequest, - GetProofForCommitmentResponse, GetTransactionByHashRequest, GetTransactionByHashResponse, + GetLastBlockRequest, GetLastBlockResponse, GetProgramIdsRequest, GetProgramIdsResponse, + GetProofForCommitmentRequest, GetProofForCommitmentResponse, GetTransactionByHashRequest, + GetTransactionByHashResponse, }; use crate::sequencer_client::json::AccountInitialData; use crate::transaction::{EncodedTransaction, NSSATransaction}; @@ -77,6 +78,19 @@ impl SequencerClient { Ok(resp_deser) } + ///Get last known `blokc_id` from sequencer + pub async fn get_last_block(&self) -> Result { + let block_req = GetLastBlockRequest {}; + + let req = serde_json::to_value(block_req)?; + + let resp = self.call_method_with_payload("get_last_block", req).await?; + + let resp_deser = serde_json::from_value(resp)?; + + Ok(resp_deser) + } + ///Get account public balance for `address`. `address` must be a valid hex-string for 32 bytes. pub async fn get_account_balance( &self, @@ -241,7 +255,26 @@ impl SequencerClient { Ok(resp_deser) } - // Get Ids of the programs used by the node + pub async fn send_tx_program( + &self, + transaction: nssa::ProgramDeploymentTransaction, + ) -> Result { + let transaction = EncodedTransaction::from(NSSATransaction::ProgramDeployment(transaction)); + + let tx_req = SendTxRequest { + transaction: borsh::to_vec(&transaction).unwrap(), + }; + + let req = serde_json::to_value(tx_req)?; + + let resp = self.call_method_with_payload("send_tx", req).await?; + + let resp_deser = serde_json::from_value(resp)?; + + Ok(resp_deser) + } + + /// Get Ids of the programs used by the node pub async fn get_program_ids( &self, ) -> Result, SequencerClientError> { diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 31486b3..1bcf661 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -10,6 +10,7 @@ pub type HashType = [u8; 32]; pub enum NSSATransaction { Public(nssa::PublicTransaction), PrivacyPreserving(nssa::PrivacyPreservingTransaction), + ProgramDeployment(nssa::ProgramDeploymentTransaction), } impl From for NSSATransaction { @@ -24,12 +25,19 @@ impl From for NSSATransaction { } } +impl From for NSSATransaction { + fn from(value: nssa::ProgramDeploymentTransaction) -> Self { + Self::ProgramDeployment(value) + } +} + #[derive( Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize, )] pub enum TxKind { Public, PrivacyPreserving, + ProgramDeployment, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] @@ -51,6 +59,10 @@ impl From for EncodedTransaction { tx_kind: TxKind::PrivacyPreserving, encoded_transaction_data: tx.to_bytes(), }, + NSSATransaction::ProgramDeployment(tx) => Self { + tx_kind: TxKind::ProgramDeployment, + encoded_transaction_data: tx.to_bytes(), + }, } } } @@ -66,6 +78,10 @@ impl TryFrom<&EncodedTransaction> for NSSATransaction { nssa::PrivacyPreservingTransaction::from_bytes(&value.encoded_transaction_data) .map(|tx| tx.into()) } + TxKind::ProgramDeployment => { + nssa::ProgramDeploymentTransaction::from_bytes(&value.encoded_transaction_data) + .map(|tx| tx.into()) + } } } } diff --git a/integration_tests/configs/debug/sequencer/sequencer_config.json b/integration_tests/configs/debug/sequencer/sequencer_config.json index 87624ae..2a2037d 100644 --- a/integration_tests/configs/debug/sequencer/sequencer_config.json +++ b/integration_tests/configs/debug/sequencer/sequencer_config.json @@ -8,11 +8,11 @@ "port": 3040, "initial_accounts": [ { - "addr": "0eee24287296ba55278f1e5403be014754866366388730303c2889be17ada065", + "addr": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44", "balance": 10000 }, { - "addr": "9e3d8e654d440e95293aa2dceceb137899a59535e952f747068e7a0ee30965f2", + "addr": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc", "balance": 20000 } ], diff --git a/integration_tests/configs/debug/wallet/wallet_config.json b/integration_tests/configs/debug/wallet/wallet_config.json index 09209f6..0081da6 100644 --- a/integration_tests/configs/debug/wallet/wallet_config.json +++ b/integration_tests/configs/debug/wallet/wallet_config.json @@ -9,7 +9,7 @@ "initial_accounts": [ { "Public": { - "address": "0eee24287296ba55278f1e5403be014754866366388730303c2889be17ada065", + "address": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44", "pub_sign_key": [ 1, 1, @@ -48,7 +48,7 @@ }, { "Public": { - "address": "9e3d8e654d440e95293aa2dceceb137899a59535e952f747068e7a0ee30965f2", + "address": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc", "pub_sign_key": [ 2, 2, @@ -87,7 +87,7 @@ }, { "Private": { - "address": "9cb6b0035320266e430eac9d96745769e7efcf30d2b9cc21ff000b3f873dc2a8", + "address": "d360d6b5763f71ac6af56253687fd7d556d5c6c64312e53c0b92ef039a4375df", "account": { "program_owner": [ 0, @@ -316,7 +316,7 @@ }, { "Private": { - "address": "a55f4f98d2f265c91d8a9868564242d8070b9bf7180a29363f52eb76988636fd", + "address": "f27087ffc29b99035303697dcf6c8e323b1847d4261e6afd49e0d71c6dfa31ea", "account": { "program_owner": [ 0, diff --git a/integration_tests/src/data_changer.bin b/integration_tests/src/data_changer.bin new file mode 100644 index 0000000..d201f91 Binary files /dev/null and b/integration_tests/src/data_changer.bin differ diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index b8303a7..ec04130 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -9,7 +9,7 @@ use common::{ transaction::{EncodedTransaction, NSSATransaction}, }; use log::{info, warn}; -use nssa::{Address, PrivacyPreservingTransaction, program::Program}; +use nssa::{Address, PrivacyPreservingTransaction, ProgramDeploymentTransaction, program::Program}; use nssa_core::{ Commitment, NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point, }; @@ -19,9 +19,20 @@ use tempfile::TempDir; use tokio::task::JoinHandle; use wallet::{ Command, SubcommandReturnValue, WalletCore, - cli::token_program::{ - TokenProgramSubcommand, TokenProgramSubcommandDeshielded, TokenProgramSubcommandPrivate, - TokenProgramSubcommandPublic, TokenProgramSubcommandShielded, + cli::{ + account::{AccountSubcommand, FetchSubcommand, RegisterSubcommand}, + native_token_transfer_program::{ + NativeTokenTransferProgramSubcommand, NativeTokenTransferProgramSubcommandPrivate, + NativeTokenTransferProgramSubcommandShielded, + }, + pinata_program::{ + PinataProgramSubcommand, PinataProgramSubcommandPrivate, PinataProgramSubcommandPublic, + }, + token_program::{ + TokenProgramSubcommand, TokenProgramSubcommandDeshielded, + TokenProgramSubcommandPrivate, TokenProgramSubcommandPublic, + TokenProgramSubcommandShielded, + }, }, config::PersistentAccountData, helperfunctions::{fetch_config, fetch_persistent_accounts}, @@ -36,16 +47,18 @@ struct Args { test_name: String, } -pub const ACC_SENDER: &str = "0eee24287296ba55278f1e5403be014754866366388730303c2889be17ada065"; -pub const ACC_RECEIVER: &str = "9e3d8e654d440e95293aa2dceceb137899a59535e952f747068e7a0ee30965f2"; +pub const ACC_SENDER: &str = "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44"; +pub const ACC_RECEIVER: &str = "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc"; pub const ACC_SENDER_PRIVATE: &str = - "9cb6b0035320266e430eac9d96745769e7efcf30d2b9cc21ff000b3f873dc2a8"; + "d360d6b5763f71ac6af56253687fd7d556d5c6c64312e53c0b92ef039a4375df"; pub const ACC_RECEIVER_PRIVATE: &str = - "a55f4f98d2f265c91d8a9868564242d8070b9bf7180a29363f52eb76988636fd"; + "f27087ffc29b99035303697dcf6c8e323b1847d4261e6afd49e0d71c6dfa31ea"; pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; +pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &[u8] = include_bytes!("data_changer.bin"); + #[allow(clippy::type_complexity)] pub async fn pre_test( home_dir: PathBuf, @@ -101,13 +114,13 @@ pub async fn post_test(residual: (ServerHandle, JoinHandle>, TempDir) pub async fn test_success() { info!("test_success"); - let command = Command::SendNativeTokenTransferPublic { + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Public { from: ACC_SENDER.to_string(), to: ACC_RECEIVER.to_string(), amount: 100, - }; + }); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); @@ -137,15 +150,15 @@ pub async fn test_success() { pub async fn test_success_move_to_another_account() { info!("test_success_move_to_another_account"); - let command = Command::RegisterAccountPublic {}; + let command = Command::Account(AccountSubcommand::Register(RegisterSubcommand::Public {})); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); wallet::execute_subcommand(command).await.unwrap(); - let persistent_accounts = fetch_persistent_accounts().unwrap(); + let persistent_accounts = fetch_persistent_accounts().await.unwrap(); let mut new_persistent_account_addr = String::new(); @@ -161,11 +174,11 @@ pub async fn test_success_move_to_another_account() { panic!("Failed to produce new account, not present in persistent accounts"); } - let command = Command::SendNativeTokenTransferPublic { + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Public { from: ACC_SENDER.to_string(), to: new_persistent_account_addr.clone(), amount: 100, - }; + }); wallet::execute_subcommand(command).await.unwrap(); @@ -193,13 +206,13 @@ pub async fn test_success_move_to_another_account() { pub async fn test_failure() { info!("test_failure"); - let command = Command::SendNativeTokenTransferPublic { + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Public { from: ACC_SENDER.to_string(), to: ACC_RECEIVER.to_string(), amount: 1000000, - }; + }); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); @@ -231,13 +244,13 @@ pub async fn test_failure() { pub async fn test_success_two_transactions() { info!("test_success_two_transactions"); - let command = Command::SendNativeTokenTransferPublic { + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Public { from: ACC_SENDER.to_string(), to: ACC_RECEIVER.to_string(), amount: 100, - }; + }); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); @@ -264,11 +277,11 @@ pub async fn test_success_two_transactions() { info!("First TX Success!"); - let command = Command::SendNativeTokenTransferPublic { + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Public { from: ACC_SENDER.to_string(), to: ACC_RECEIVER.to_string(), amount: 100, - }; + }); wallet::execute_subcommand(command).await.unwrap(); @@ -296,7 +309,7 @@ pub async fn test_success_two_transactions() { pub async fn test_get_account() { info!("test_get_account"); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); let account = seq_client @@ -317,22 +330,28 @@ pub async fn test_get_account() { /// This test creates a new token using the token program. After creating the token, the test executes a /// token transfer to a new account. pub async fn test_success_token_program() { - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); // Create new account for the token definition - wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap(); + wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Public {}, + ))) + .await + .unwrap(); // Create new account for the token supply holder - wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap(); + wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Public {}, + ))) + .await + .unwrap(); // Create new account for receiving a token transaction - wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap(); + wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Public {}, + ))) + .await + .unwrap(); - let persistent_accounts = fetch_persistent_accounts().unwrap(); + let persistent_accounts = fetch_persistent_accounts().await.unwrap(); let mut new_persistent_accounts_addr = Vec::new(); @@ -458,31 +477,35 @@ pub async fn test_success_token_program() { /// 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. pub async fn test_success_token_program_private_owned() { - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { addr: definition_addr, - } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap() + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Public {}, + ))) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = - wallet::execute_subcommand(Command::RegisterAccountPrivate {}) - .await - .unwrap() - else { + let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = wallet::execute_subcommand( + Command::Account(AccountSubcommand::Register(RegisterSubcommand::Private {})), + ) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { addr: recipient_addr, - } = wallet::execute_subcommand(Command::RegisterAccountPrivate {}) - .await - .unwrap() + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Private {}, + ))) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; @@ -523,8 +546,10 @@ pub async fn test_success_token_program_private_owned() { ] ); - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).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(&supply_addr) @@ -546,8 +571,10 @@ pub async fn test_success_token_program_private_owned() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).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(&supply_addr) @@ -574,8 +601,10 @@ pub async fn test_success_token_program_private_owned() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).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(&supply_addr) @@ -591,31 +620,35 @@ pub async fn test_success_token_program_private_owned() { /// 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. pub async fn test_success_token_program_private_claiming_path() { - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { addr: definition_addr, - } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap() + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Public {}, + ))) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = - wallet::execute_subcommand(Command::RegisterAccountPrivate {}) - .await - .unwrap() - else { + let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = wallet::execute_subcommand( + Command::Account(AccountSubcommand::Register(RegisterSubcommand::Private {})), + ) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { addr: recipient_addr, - } = wallet::execute_subcommand(Command::RegisterAccountPrivate {}) - .await - .unwrap() + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Private {}, + ))) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; @@ -656,8 +689,10 @@ pub async fn test_success_token_program_private_claiming_path() { ] ); - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).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(&supply_addr) @@ -691,16 +726,18 @@ pub async fn test_success_token_program_private_claiming_path() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let command = Command::FetchPrivateAccount { + let command = Command::Account(AccountSubcommand::Fetch(FetchSubcommand::PrivateAccount { tx_hash, acc_addr: recipient_addr.to_string(), output_id: 1, - }; + })); wallet::execute_subcommand(command).await.unwrap(); - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).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(&supply_addr) @@ -716,31 +753,35 @@ pub async fn test_success_token_program_private_claiming_path() { /// This test creates a new public token using the token program. After creating the token, the test executes a /// shielded token transfer to a new account. All accounts are owned except definition. pub async fn test_success_token_program_shielded_owned() { - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { addr: definition_addr, - } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap() + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Public {}, + ))) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = - wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap() - else { + // Create new account for the token supply holder (public) + let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = wallet::execute_subcommand( + Command::Account(AccountSubcommand::Register(RegisterSubcommand::Public {})), + ) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { addr: recipient_addr, - } = wallet::execute_subcommand(Command::RegisterAccountPrivate {}) - .await - .unwrap() + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Private {}, + ))) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; @@ -795,8 +836,10 @@ pub async fn test_success_token_program_shielded_owned() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + let wallet_config = fetch_config().await.unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let new_commitment2 = wallet_storage .get_private_account_commitment(&recipient_addr) @@ -819,8 +862,10 @@ pub async fn test_success_token_program_shielded_owned() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + let wallet_config = fetch_config().await.unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let new_commitment2 = wallet_storage .get_private_account_commitment(&recipient_addr) @@ -831,31 +876,35 @@ pub async fn test_success_token_program_shielded_owned() { /// This test creates a new private token using the token program. After creating the token, the test executes a /// deshielded token transfer to a new account. All accounts are owned except definition. pub async fn test_success_token_program_deshielded_owned() { - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { addr: definition_addr, - } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap() + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Public {}, + ))) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = - wallet::execute_subcommand(Command::RegisterAccountPrivate {}) - .await - .unwrap() - else { + let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = wallet::execute_subcommand( + Command::Account(AccountSubcommand::Register(RegisterSubcommand::Private {})), + ) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { addr: recipient_addr, - } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) - .await - .unwrap() + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::Register( + RegisterSubcommand::Public {}, + ))) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; @@ -896,8 +945,10 @@ pub async fn test_success_token_program_deshielded_owned() { ] ); - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).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(&supply_addr) @@ -920,8 +971,10 @@ pub async fn test_success_token_program_deshielded_owned() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).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(&supply_addr) @@ -944,8 +997,10 @@ pub async fn test_success_token_program_deshielded_owned() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).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(&supply_addr) @@ -958,20 +1013,24 @@ pub async fn test_success_private_transfer_to_another_owned_account() { let from: Address = ACC_SENDER_PRIVATE.parse().unwrap(); let to: Address = ACC_RECEIVER_PRIVATE.parse().unwrap(); - let command = Command::SendNativeTokenTransferPrivateOwnedAccount { - from: from.to_string(), - to: to.to_string(), - amount: 100, - }; + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Private( + NativeTokenTransferProgramSubcommandPrivate::PrivateOwned { + from: from.to_string(), + to: to.to_string(), + amount: 100, + }, + )); wallet::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().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).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let new_commitment1 = wallet_storage .get_private_account_commitment(&from) @@ -991,12 +1050,14 @@ pub async fn test_success_private_transfer_to_another_foreign_account() { let to_npk_string = hex::encode(to_npk.0); let to_ipk = Secp256k1Point::from_scalar(to_npk.0); - let command = Command::SendNativeTokenTransferPrivateForeignAccount { - from: from.to_string(), - to_npk: to_npk_string, - to_ipk: hex::encode(to_ipk.0), - amount: 100, - }; + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Private( + NativeTokenTransferProgramSubcommandPrivate::PrivateForeign { + from: from.to_string(), + to_npk: to_npk_string, + to_ipk: hex::encode(to_ipk.0), + amount: 100, + }, + )); let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = wallet::execute_subcommand(command).await.unwrap() @@ -1007,9 +1068,11 @@ pub async fn test_success_private_transfer_to_another_foreign_account() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().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).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let new_commitment1 = wallet_storage .get_private_account_commitment(&from) @@ -1030,16 +1093,18 @@ pub async fn test_success_private_transfer_to_another_owned_account_claiming_pat info!("test_success_private_transfer_to_another_owned_account_claiming_path"); let from: Address = ACC_SENDER_PRIVATE.parse().unwrap(); - let command = Command::RegisterAccountPrivate {}; + let command = Command::Account(AccountSubcommand::Register(RegisterSubcommand::Private {})); let sub_ret = wallet::execute_subcommand(command).await.unwrap(); let SubcommandReturnValue::RegisterAccount { addr: to_addr } = sub_ret else { panic!("FAILED TO REGISTER ACCOUNT"); }; - let wallet_config = fetch_config().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()).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) + .await + .unwrap(); let (to_keys, _) = wallet_storage .storage @@ -1049,12 +1114,14 @@ pub async fn test_success_private_transfer_to_another_owned_account_claiming_pat .cloned() .unwrap(); - let command = Command::SendNativeTokenTransferPrivateForeignAccount { - from: from.to_string(), - to_npk: hex::encode(to_keys.nullifer_public_key.0), - to_ipk: hex::encode(to_keys.incoming_viewing_public_key.0), - amount: 100, - }; + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Private( + NativeTokenTransferProgramSubcommandPrivate::PrivateForeign { + from: from.to_string(), + to_npk: hex::encode(to_keys.nullifer_public_key.0), + to_ipk: hex::encode(to_keys.incoming_viewing_public_key.0), + amount: 100, + }, + )); let sub_ret = wallet::execute_subcommand(command).await.unwrap(); let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { @@ -1063,13 +1130,15 @@ pub async fn test_success_private_transfer_to_another_owned_account_claiming_pat let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - let command = Command::FetchPrivateAccount { + let command = Command::Account(AccountSubcommand::Fetch(FetchSubcommand::PrivateAccount { tx_hash, acc_addr: to_addr.to_string(), output_id: 1, - }; + })); wallet::execute_subcommand(command).await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let new_commitment1 = wallet_storage .get_private_account_commitment(&from) @@ -1088,19 +1157,91 @@ pub async fn test_success_private_transfer_to_another_owned_account_claiming_pat info!("Success!"); } +pub async fn test_success_private_transfer_to_another_owned_account_cont_run_path() { + info!("test_success_private_transfer_to_another_owned_account_cont_run_path"); + let continious_run_handle = tokio::spawn(wallet::execute_continious_run()); + + let from: Address = ACC_SENDER_PRIVATE.parse().unwrap(); + + let command = Command::Account(AccountSubcommand::Register(RegisterSubcommand::Private {})); + + let sub_ret = wallet::execute_subcommand(command).await.unwrap(); + let SubcommandReturnValue::RegisterAccount { addr: to_addr } = sub_ret else { + panic!("FAILED TO REGISTER ACCOUNT"); + }; + + 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 (to_keys, _) = wallet_storage + .storage + .user_data + .user_private_accounts + .get(&to_addr) + .cloned() + .unwrap(); + + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Private( + NativeTokenTransferProgramSubcommandPrivate::PrivateForeign { + from: from.to_string(), + to_npk: hex::encode(to_keys.nullifer_public_key.0), + to_ipk: hex::encode(to_keys.incoming_viewing_public_key.0), + amount: 100, + }, + )); + + let sub_ret = wallet::execute_subcommand(command).await.unwrap(); + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { + panic!("FAILED TO SEND TX"); + }; + + let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; + + println!("Waiting for next blocks to check if continoius run fetch account"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&from) + .unwrap(); + assert_eq!(tx.message.new_commitments[0], new_commitment1); + + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, &seq_client).await); + } + + let to_res_acc = wallet_storage.get_account_private(&to_addr).unwrap(); + + assert_eq!(to_res_acc.balance, 100); + + continious_run_handle.abort(); + + info!("Success!"); +} + pub async fn test_success_deshielded_transfer_to_another_account() { info!("test_success_deshielded_transfer_to_another_account"); let from: Address = ACC_SENDER_PRIVATE.parse().unwrap(); let to: Address = ACC_RECEIVER.parse().unwrap(); - let command = Command::SendNativeTokenTransferDeshielded { + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Deshielded { from: from.to_string(), to: to.to_string(), amount: 100, - }; + }); - let wallet_config = fetch_config().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()).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) + .await + .unwrap(); let from_acc = wallet_storage.get_account_private(&from).unwrap(); assert_eq!(from_acc.balance, 10000); @@ -1110,7 +1251,9 @@ pub async fn test_success_deshielded_transfer_to_another_account() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let from_acc = wallet_storage.get_account_private(&from).unwrap(); let new_commitment = wallet_storage @@ -1133,13 +1276,15 @@ pub async fn test_success_shielded_transfer_to_another_owned_account() { info!("test_success_shielded_transfer_to_another_owned_account"); let from: Address = ACC_SENDER.parse().unwrap(); let to: Address = ACC_RECEIVER_PRIVATE.parse().unwrap(); - let command = Command::SendNativeTokenTransferShielded { - from: from.to_string(), - to: to.to_string(), - amount: 100, - }; + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Shielded( + NativeTokenTransferProgramSubcommandShielded::ShieldedOwned { + from: from.to_string(), + to: to.to_string(), + amount: 100, + }, + )); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); wallet::execute_subcommand(command).await.unwrap(); @@ -1147,8 +1292,10 @@ pub async fn test_success_shielded_transfer_to_another_owned_account() { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let wallet_config = fetch_config().unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + let wallet_config = fetch_config().await.unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let acc_to = wallet_storage.get_account_private(&to).unwrap(); let new_commitment = wallet_storage.get_private_account_commitment(&to).unwrap(); @@ -1172,14 +1319,16 @@ pub async fn test_success_shielded_transfer_to_another_foreign_account() { let to_ipk = Secp256k1Point::from_scalar(to_npk.0); let from: Address = ACC_SENDER.parse().unwrap(); - let command = Command::SendNativeTokenTransferShieldedForeignAccount { - from: from.to_string(), - to_npk: to_npk_string, - to_ipk: hex::encode(to_ipk.0), - amount: 100, - }; + let command = Command::Transfer(NativeTokenTransferProgramSubcommand::Shielded( + NativeTokenTransferProgramSubcommandShielded::ShieldedForeign { + from: from.to_string(), + to_npk: to_npk_string, + to_ipk: hex::encode(to_ipk.0), + amount: 100, + }, + )); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); @@ -1213,13 +1362,15 @@ pub async fn test_pinata() { let pinata_addr = "cafe".repeat(16); let pinata_prize = 150; let solution = 989106; - let command = Command::ClaimPinata { - pinata_addr: pinata_addr.clone(), - winner_addr: ACC_SENDER.to_string(), - solution, - }; + let command = Command::PinataProgram(PinataProgramSubcommand::Public( + PinataProgramSubcommandPublic::Claim { + pinata_addr: pinata_addr.clone(), + winner_addr: ACC_SENDER.to_string(), + solution, + }, + )); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); @@ -1253,6 +1404,48 @@ pub async fn test_pinata() { info!("Success!"); } +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 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; + + // The program is the data changer and takes one account as input. + // 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 data_changer = Program::new(bytecode).unwrap(); + let address: Address = "deadbeef".repeat(8).parse().unwrap(); + let message = + nssa::public_transaction::Message::try_new(data_changer.id(), vec![address], vec![], ()) + .unwrap(); + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let transaction = nssa::PublicTransaction::new(message, witness_set); + let _response = seq_client.send_tx_public(transaction).await.unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let post_state_account = seq_client + .get_account(address.to_string()) + .await + .unwrap() + .account; + assert_eq!(post_state_account.program_owner, data_changer.id()); + assert_eq!(post_state_account.balance, 0); + assert_eq!(post_state_account.data, vec![0]); + assert_eq!(post_state_account.nonce, 0); + + info!("Success!"); +} + pub async fn test_authenticated_transfer_initialize_function() { info!("test initialize account for authenticated transfer"); let command = Command::AuthenticatedTransferInitializePublicAccount {}; @@ -1264,7 +1457,7 @@ pub async fn test_authenticated_transfer_initialize_function() { }; info!("Checking correct execution"); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); let account = seq_client .get_account(addr.to_string()) @@ -1280,6 +1473,7 @@ pub async fn test_authenticated_transfer_initialize_function() { assert_eq!(account.balance, expected_balance); assert_eq!(account.nonce, expected_nonce); assert!(account.data.is_empty()); + info!("Success!"); } @@ -1289,13 +1483,15 @@ pub async fn test_pinata_private_receiver() { let pinata_prize = 150; let solution = 989106; - let command = Command::ClaimPinataPrivateReceiverOwned { - pinata_addr: pinata_addr.clone(), - winner_addr: ACC_SENDER_PRIVATE.to_string(), - solution, - }; + let command = Command::PinataProgram(PinataProgramSubcommand::Private( + PinataProgramSubcommandPrivate::ClaimPrivateOwned { + pinata_addr: pinata_addr.clone(), + winner_addr: ACC_SENDER_PRIVATE.to_string(), + solution, + }, + )); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); @@ -1321,16 +1517,18 @@ pub async fn test_pinata_private_receiver() { .unwrap() .balance; - let command = Command::FetchPrivateAccount { + let command = Command::Account(AccountSubcommand::Fetch(FetchSubcommand::PrivateAccount { tx_hash: tx_hash.clone(), acc_addr: ACC_SENDER_PRIVATE.to_string(), output_id: 0, - }; + })); wallet::execute_subcommand(command).await.unwrap(); - let wallet_config = fetch_config().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).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let new_commitment1 = wallet_storage .get_private_account_commitment(&ACC_SENDER_PRIVATE.parse().unwrap()) @@ -1349,21 +1547,23 @@ pub async fn test_pinata_private_receiver_new_account() { let solution = 989106; // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { addr: winner_addr } = - wallet::execute_subcommand(Command::RegisterAccountPrivate {}) - .await - .unwrap() - else { + let SubcommandReturnValue::RegisterAccount { addr: winner_addr } = wallet::execute_subcommand( + Command::Account(AccountSubcommand::Register(RegisterSubcommand::Private {})), + ) + .await + .unwrap() else { panic!("invalid subcommand return value"); }; - let command = Command::ClaimPinataPrivateReceiverOwned { - pinata_addr: pinata_addr.clone(), - winner_addr: winner_addr.to_string(), - solution, - }; + let command = Command::PinataProgram(PinataProgramSubcommand::Private( + PinataProgramSubcommandPrivate::ClaimPrivateOwned { + pinata_addr: pinata_addr.clone(), + winner_addr: winner_addr.to_string(), + solution, + }, + )); - let wallet_config = fetch_config().unwrap(); + let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); @@ -1385,9 +1585,11 @@ pub async fn test_pinata_private_receiver_new_account() { .unwrap() .balance; - let wallet_config = fetch_config().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).unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) + .await + .unwrap(); let new_commitment1 = wallet_storage .get_private_account_commitment(&winner_addr) @@ -1479,6 +1681,9 @@ pub async fn main_tests_runner() -> Result<()> { "test_pinata" => { test_cleanup_wrap!(home_dir, test_pinata); } + "test_program_deployment" => { + test_cleanup_wrap!(home_dir, test_program_deployment); + } "test_authenticated_transfer_initialize_function" => { test_cleanup_wrap!(home_dir, test_authenticated_transfer_initialize_function); } @@ -1494,6 +1699,12 @@ pub async fn main_tests_runner() -> Result<()> { "test_pinata_private_receiver_new_account" => { test_cleanup_wrap!(home_dir, test_pinata_private_receiver_new_account); } + "test_success_private_transfer_to_another_owned_account_cont_run_path" => { + test_cleanup_wrap!( + home_dir, + test_success_private_transfer_to_another_owned_account_cont_run_path + ); + } "test_success_token_program_shielded_owned" => { test_cleanup_wrap!(home_dir, test_success_token_program_shielded_owned); } @@ -1533,12 +1744,17 @@ pub async fn main_tests_runner() -> Result<()> { ); test_cleanup_wrap!(home_dir, test_success_token_program_shielded_owned); test_cleanup_wrap!(home_dir, test_pinata); + test_cleanup_wrap!(home_dir, test_program_deployment); test_cleanup_wrap!(home_dir, test_authenticated_transfer_initialize_function); test_cleanup_wrap!(home_dir, test_pinata_private_receiver); test_cleanup_wrap!(home_dir, test_success_token_program_private_owned); test_cleanup_wrap!(home_dir, test_success_token_program_deshielded_owned); test_cleanup_wrap!(home_dir, test_success_token_program_private_claiming_path); test_cleanup_wrap!(home_dir, test_pinata_private_receiver_new_account); + test_cleanup_wrap!( + home_dir, + test_success_private_transfer_to_another_owned_account_cont_run_path + ); } "all_private" => { test_cleanup_wrap!( @@ -1569,6 +1785,10 @@ pub async fn main_tests_runner() -> Result<()> { test_cleanup_wrap!(home_dir, test_success_token_program_private_owned); test_cleanup_wrap!(home_dir, test_success_token_program_private_claiming_path); test_cleanup_wrap!(home_dir, test_pinata_private_receiver_new_account); + test_cleanup_wrap!( + home_dir, + test_success_private_transfer_to_another_owned_account_cont_run_path + ); } _ => { anyhow::bail!("Unknown test name"); diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index a86bd98..915212c 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -14,6 +14,7 @@ secp256k1 = "0.31.1" rand = "0.8" borsh = "1.5.7" hex = "0.4.3" +risc0-binfmt = "3.0.2" [build-dependencies] risc0-build = "3.0.3" diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs index f2dc18e..280451b 100644 --- a/nssa/core/src/encryption/mod.rs +++ b/nssa/core/src/encryption/mod.rs @@ -54,7 +54,7 @@ impl EncryptionScheme { ) -> [u8; 32] { let mut bytes = Vec::new(); - bytes.extend_from_slice(b"NSSA/v0.1/KDF-SHA256"); + bytes.extend_from_slice(b"NSSA/v0.2/KDF-SHA256/"); bytes.extend_from_slice(&shared_secret.0); bytes.extend_from_slice(&commitment.to_byte_array()); bytes.extend_from_slice(&output_index.to_le_bytes()); diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index 5e3e208..a1bc38c 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -9,7 +9,7 @@ pub struct NullifierPublicKey(pub [u8; 32]); impl From<&NullifierPublicKey> for AccountId { fn from(value: &NullifierPublicKey) -> Self { - const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.1/AccountId/Private/\x00\x00\x00"; + const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.2/AccountId/Private/\x00\x00\x00"; let mut bytes = [0; 64]; bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX); @@ -46,7 +46,7 @@ pub struct Nullifier(pub(super) [u8; 32]); impl Nullifier { pub fn for_account_update(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self { - const UPDATE_PREFIX: &[u8; 32] = b"/NSSA/v0.1/Nullifier/Update/\x00\x00\x00\x00"; + const UPDATE_PREFIX: &[u8; 32] = b"/NSSA/v0.2/Nullifier/Update/\x00\x00\x00\x00"; let mut bytes = UPDATE_PREFIX.to_vec(); bytes.extend_from_slice(&commitment.to_byte_array()); bytes.extend_from_slice(nsk); @@ -54,7 +54,7 @@ impl Nullifier { } pub fn for_account_initialization(npk: &NullifierPublicKey) -> Self { - const INIT_PREFIX: &[u8; 32] = b"/NSSA/v0.1/Nullifier/Initialize/"; + const INIT_PREFIX: &[u8; 32] = b"/NSSA/v0.2/Nullifier/Initialize/"; let mut bytes = INIT_PREFIX.to_vec(); bytes.extend_from_slice(&npk.to_byte_array()); Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) @@ -70,8 +70,8 @@ mod tests { let commitment = Commitment((0..32u8).collect::>().try_into().unwrap()); let nsk = [0x42; 32]; let expected_nullifier = Nullifier([ - 235, 128, 185, 229, 74, 74, 83, 13, 165, 48, 239, 24, 48, 101, 71, 251, 253, 92, 88, - 201, 103, 43, 250, 135, 193, 54, 175, 82, 245, 171, 90, 135, + 148, 243, 116, 209, 140, 231, 211, 61, 35, 62, 114, 110, 143, 224, 82, 201, 221, 34, + 53, 80, 185, 48, 174, 28, 203, 43, 94, 187, 85, 199, 115, 81, ]); let nullifier = Nullifier::for_account_update(&commitment, &nsk); assert_eq!(nullifier, expected_nullifier); @@ -84,8 +84,8 @@ mod tests { 255, 29, 105, 42, 186, 43, 11, 157, 168, 132, 225, 17, 163, ]); let expected_nullifier = Nullifier([ - 96, 99, 33, 1, 116, 84, 169, 18, 85, 201, 17, 243, 123, 240, 242, 34, 116, 233, 92, - 203, 247, 92, 161, 162, 135, 66, 127, 108, 230, 149, 105, 157, + 1, 6, 59, 168, 16, 146, 65, 252, 255, 91, 48, 85, 116, 189, 110, 218, 110, 136, 163, + 193, 245, 103, 51, 27, 235, 170, 215, 115, 97, 144, 36, 238, ]); let nullifier = Nullifier::for_account_initialization(&npk); assert_eq!(nullifier, expected_nullifier); @@ -113,8 +113,8 @@ mod tests { ]; let npk = NullifierPublicKey::from(&nsk); let expected_account_id = AccountId::new([ - 69, 160, 50, 67, 12, 56, 150, 116, 62, 145, 17, 161, 17, 45, 24, 53, 33, 167, 83, 178, - 47, 114, 111, 233, 251, 30, 54, 244, 184, 22, 100, 236, + 18, 153, 225, 78, 35, 214, 212, 205, 152, 83, 18, 246, 69, 41, 20, 217, 85, 1, 108, 7, + 87, 133, 181, 53, 247, 221, 174, 12, 112, 194, 34, 121, ]); let account_id = AccountId::from(&npk); 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 266db1a..a1aa8c9 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -21,9 +21,6 @@ fn main() { program_id, } = env::read(); - // TODO: Check that `program_execution_proof` is one of the allowed built-in programs - // assert_eq!(program_id, AUTHENTICATED_TRANSFER_PROGRAM_ID); - // Check that `program_output` is consistent with the execution of the corresponding program. env::verify(program_id, &to_vec(&program_output).unwrap()).unwrap(); diff --git a/nssa/src/encoding/mod.rs b/nssa/src/encoding/mod.rs index 5ff45e2..7d9a5a8 100644 --- a/nssa/src/encoding/mod.rs +++ b/nssa/src/encoding/mod.rs @@ -1,2 +1,3 @@ pub mod privacy_preserving_transaction; +pub mod program_deployment_transaction; pub mod public_transaction; diff --git a/nssa/src/encoding/privacy_preserving_transaction.rs b/nssa/src/encoding/privacy_preserving_transaction.rs index 2e5ea14..5788e6f 100644 --- a/nssa/src/encoding/privacy_preserving_transaction.rs +++ b/nssa/src/encoding/privacy_preserving_transaction.rs @@ -16,8 +16,9 @@ use crate::{ }, }; -const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; -const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/"; +const MESSAGE_ENCODING_PREFIX_LEN: usize = 32; +const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = + b"/NSSA/v0.2/TxMessage/Private/\x00\x00\x00"; impl EncryptedAccountData { pub fn to_bytes(&self) -> Vec { diff --git a/nssa/src/encoding/program_deployment_transaction.rs b/nssa/src/encoding/program_deployment_transaction.rs new file mode 100644 index 0000000..2dc91b4 --- /dev/null +++ b/nssa/src/encoding/program_deployment_transaction.rs @@ -0,0 +1,86 @@ +// TODO: Consider switching to deriving Borsh + +use std::io::{Cursor, Read}; + +use crate::{ + ProgramDeploymentTransaction, error::NssaError, program_deployment_transaction::Message, +}; + +const MESSAGE_ENCODING_PREFIX_LEN: usize = 32; +const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = + b"/NSSA/v0.2/TxMessage/Program/\x00\x00\x00"; + +impl Message { + /// Serializes a `Message` into bytes in the following layout: + /// PREFIX || bytecode_len (4 bytes LE) || + /// Integers are encoded in little-endian byte order, and fields appear in the above order. + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec(); + let bytecode_len = self.bytecode.len() as u32; + bytes.extend(&bytecode_len.to_le_bytes()); + bytes.extend(&self.bytecode); + bytes + } + + pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let prefix = { + let mut this = [0u8; MESSAGE_ENCODING_PREFIX_LEN]; + cursor.read_exact(&mut this)?; + this + }; + if &prefix != MESSAGE_ENCODING_PREFIX { + return Err(NssaError::TransactionDeserializationError( + "Invalid public message prefix".to_string(), + )); + } + let bytecode_len = u32_from_cursor(cursor)?; + let mut bytecode = vec![0; bytecode_len as usize]; + let num_bytes = cursor.read(&mut bytecode)?; + if num_bytes != bytecode_len as usize { + println!("num bytes: {}", num_bytes); + return Err(NssaError::TransactionDeserializationError( + "Invalid number of bytes".to_string(), + )); + } + Ok(Self { bytecode }) + } +} + +impl ProgramDeploymentTransaction { + pub fn to_bytes(&self) -> Vec { + self.message.to_bytes() + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); + Self::from_cursor(&mut cursor) + } + + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let message = Message::from_cursor(cursor)?; + Ok(Self::new(message)) + } +} + +fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let mut word_buf = [0u8; 4]; + cursor.read_exact(&mut word_buf)?; + Ok(u32::from_le_bytes(word_buf)) +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use crate::{ProgramDeploymentTransaction, program_deployment_transaction::Message}; + + #[test] + fn test_roundtrip() { + let message = Message::new(vec![0xca, 0xfe, 0xca, 0xfe, 0x01, 0x02, 0x03]); + let tx = ProgramDeploymentTransaction::new(message); + let bytes = tx.to_bytes(); + let mut cursor = Cursor::new(bytes.as_ref()); + let tx_from_cursor = ProgramDeploymentTransaction::from_cursor(&mut cursor).unwrap(); + assert_eq!(tx, tx_from_cursor); + } +} diff --git a/nssa/src/encoding/public_transaction.rs b/nssa/src/encoding/public_transaction.rs index e8890de..03c34ee 100644 --- a/nssa/src/encoding/public_transaction.rs +++ b/nssa/src/encoding/public_transaction.rs @@ -10,8 +10,9 @@ use crate::{ public_transaction::{Message, WitnessSet}, }; -const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; -const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x00/NSSA/v0.1/TxMessage/"; +const MESSAGE_ENCODING_PREFIX_LEN: usize = 32; +const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = + b"/NSSA/v0.2/TxMessage/Public/\x00\x00\x00\x00"; impl Message { /// Serializes a `Message` into bytes in the following layout: diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 0e85789..8ed9657 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -48,4 +48,10 @@ pub enum NssaError { #[error("Circuit proving error")] CircuitProvingError(String), + + #[error("Invalid program bytecode")] + InvalidProgramBytecode, + + #[error("Program already exists")] + ProgramAlreadyExists, } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index dc6bb15..2fe13b9 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -12,6 +12,7 @@ pub mod error; mod merkle_tree; pub mod privacy_preserving_transaction; pub mod program; +pub mod program_deployment_transaction; pub mod public_transaction; mod signature; mod state; @@ -21,9 +22,10 @@ pub use nssa_core::address::Address; pub use privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit::execute_and_prove, }; +pub use program_deployment_transaction::ProgramDeploymentTransaction; pub use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; pub use public_transaction::PublicTransaction; pub use signature::PrivateKey; pub use signature::PublicKey; pub use signature::Signature; -pub use state::V01State; +pub use state::V02State; diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 0bcb02d..5911838 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -31,10 +31,10 @@ impl EncryptedAccountData { } } - /// Computes the tag as the first byte of SHA256("/NSSA/v0.1/ViewTag" || Npk || Ivk) + /// Computes the tag as the first byte of SHA256("/NSSA/v0.2/ViewTag/" || Npk || Ivk) pub fn compute_view_tag(npk: NullifierPublicKey, ivk: IncomingViewingPublicKey) -> ViewTag { let mut hasher = Sha256::new(); - hasher.update(b"/NSSA/v0.1/ViewTag"); + hasher.update(b"/NSSA/v0.2/ViewTag/"); hasher.update(npk.to_byte_array()); hasher.update(ivk.to_bytes()); let digest: [u8; 32] = hasher.finalize().into(); @@ -166,7 +166,7 @@ pub mod tests { let expected_view_tag = { let mut hasher = Sha256::new(); - hasher.update(b"/NSSA/v0.1/ViewTag"); + hasher.update(b"/NSSA/v0.2/ViewTag/"); hasher.update(npk.to_byte_array()); hasher.update(ivk.to_bytes()); let digest: [u8; 32] = hasher.finalize().into(); diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index cdf02c2..3e89ba7 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -8,7 +8,7 @@ use nssa_core::{ use crate::error::NssaError; use crate::privacy_preserving_transaction::circuit::Proof; use crate::privacy_preserving_transaction::message::EncryptedAccountData; -use crate::{Address, V01State}; +use crate::{Address, V02State}; use super::message::Message; use super::witness_set::WitnessSet; @@ -29,7 +29,7 @@ impl PrivacyPreservingTransaction { pub(crate) fn validate_and_produce_public_state_diff( &self, - state: &mut V01State, + state: &V02State, ) -> Result, NssaError> { let message = &self.message; let witness_set = &self.witness_set; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index f6df8fe..7771aaf 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -1,29 +1,41 @@ -use crate::program_methods::{ - AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF, - TOKEN_ID, -}; +use crate::program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}; use nssa_core::{ account::{Account, AccountWithMetadata}, program::{InstructionData, ProgramId, ProgramOutput}, }; + use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; use crate::error::NssaError; +/// 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)] pub struct Program { id: ProgramId, - elf: &'static [u8], + elf: Vec, } impl Program { + pub fn new(bytecode: Vec) -> Result { + let binary = risc0_binfmt::ProgramBinary::decode(&bytecode) + .map_err(|_| NssaError::InvalidProgramBytecode)?; + let id = binary + .compute_image_id() + .map_err(|_| NssaError::InvalidProgramBytecode)? + .into(); + Ok(Self { elf: bytecode, id }) + } + pub fn id(&self) -> ProgramId { self.id } - pub(crate) fn elf(&self) -> &'static [u8] { - self.elf + pub fn elf(&self) -> &[u8] { + &self.elf } pub fn serialize_instruction( @@ -39,13 +51,14 @@ impl Program { ) -> Result, NssaError> { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); + env_builder.session_limit(Some(MAX_NUM_CYCLES_PUBLIC_EXECUTION)); Self::write_inputs(pre_states, instruction_data, &mut env_builder)?; let env = env_builder.build().unwrap(); // Execute the program (without proving) let executor = default_executor(); let session_info = executor - .execute(env, self.elf) + .execute(env, self.elf()) .map_err(|e| NssaError::ProgramExecutionFailed(e.to_string()))?; // Get outputs @@ -71,33 +84,34 @@ impl Program { } pub fn authenticated_transfer_program() -> Self { - Self { - id: AUTHENTICATED_TRANSFER_ID, - elf: AUTHENTICATED_TRANSFER_ELF, - } + // This unwrap won't panic since the `AUTHENTICATED_TRANSFER_ELF` comes from risc0 build of + // `program_methods` + Self::new(AUTHENTICATED_TRANSFER_ELF.to_vec()).unwrap() } pub fn token() -> Self { - Self { - id: TOKEN_ID, - elf: TOKEN_ELF, - } + // This unwrap won't panic since the `TOKEN_ELF` comes from risc0 build of + // `program_methods` + Self::new(TOKEN_ELF.to_vec()).unwrap() } } // TODO: Testnet only. Refactor to prevent compilation on mainnet. impl Program { pub fn pinata() -> Self { - Self { - id: PINATA_ID, - elf: PINATA_ELF, - } + // This unwrap won't panic since the `PINATA_ELF` comes from risc0 build of + // `program_methods` + Self::new(PINATA_ELF.to_vec()).unwrap() } } #[cfg(test)] mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata}; + use program_methods::{ + AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF, + TOKEN_ID, + }; use crate::program::Program; @@ -108,7 +122,7 @@ mod tests { Program { id: NONCE_CHANGER_ID, - elf: NONCE_CHANGER_ELF, + elf: NONCE_CHANGER_ELF.to_vec(), } } @@ -118,7 +132,7 @@ mod tests { Program { id: EXTRA_OUTPUT_ID, - elf: EXTRA_OUTPUT_ELF, + elf: EXTRA_OUTPUT_ELF.to_vec(), } } @@ -128,7 +142,7 @@ mod tests { Program { id: MISSING_OUTPUT_ID, - elf: MISSING_OUTPUT_ELF, + elf: MISSING_OUTPUT_ELF.to_vec(), } } @@ -138,7 +152,7 @@ mod tests { Program { id: PROGRAM_OWNER_CHANGER_ID, - elf: PROGRAM_OWNER_CHANGER_ELF, + elf: PROGRAM_OWNER_CHANGER_ELF.to_vec(), } } @@ -148,7 +162,7 @@ mod tests { Program { id: SIMPLE_BALANCE_TRANSFER_ID, - elf: SIMPLE_BALANCE_TRANSFER_ELF, + elf: SIMPLE_BALANCE_TRANSFER_ELF.to_vec(), } } @@ -158,7 +172,7 @@ mod tests { Program { id: DATA_CHANGER_ID, - elf: DATA_CHANGER_ELF, + elf: DATA_CHANGER_ELF.to_vec(), } } @@ -168,7 +182,7 @@ mod tests { Program { id: MINTER_ID, - elf: MINTER_ELF, + elf: MINTER_ELF.to_vec(), } } @@ -178,7 +192,7 @@ mod tests { Program { id: BURNER_ID, - elf: BURNER_ELF, + elf: BURNER_ELF.to_vec(), } } } @@ -216,4 +230,18 @@ mod tests { assert_eq!(sender_post, expected_sender_post); assert_eq!(recipient_post, expected_recipient_post); } + + #[test] + fn test_builtin_programs() { + let auth_transfer_program = Program::authenticated_transfer_program(); + let token_program = Program::token(); + let pinata_program = Program::pinata(); + + assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID); + assert_eq!(auth_transfer_program.elf, AUTHENTICATED_TRANSFER_ELF); + assert_eq!(token_program.id, TOKEN_ID); + assert_eq!(token_program.elf, TOKEN_ELF); + assert_eq!(pinata_program.id, PINATA_ID); + assert_eq!(pinata_program.elf, PINATA_ELF); + } } diff --git a/nssa/src/program_deployment_transaction/message.rs b/nssa/src/program_deployment_transaction/message.rs new file mode 100644 index 0000000..6a5c670 --- /dev/null +++ b/nssa/src/program_deployment_transaction/message.rs @@ -0,0 +1,10 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Message { + pub(crate) bytecode: Vec, +} + +impl Message { + pub fn new(bytecode: Vec) -> Self { + Self { bytecode } + } +} diff --git a/nssa/src/program_deployment_transaction/mod.rs b/nssa/src/program_deployment_transaction/mod.rs new file mode 100644 index 0000000..b498826 --- /dev/null +++ b/nssa/src/program_deployment_transaction/mod.rs @@ -0,0 +1,5 @@ +mod message; +mod transaction; + +pub use message::Message; +pub use transaction::ProgramDeploymentTransaction; diff --git a/nssa/src/program_deployment_transaction/transaction.rs b/nssa/src/program_deployment_transaction/transaction.rs new file mode 100644 index 0000000..4ec2e10 --- /dev/null +++ b/nssa/src/program_deployment_transaction/transaction.rs @@ -0,0 +1,27 @@ +use crate::{ + V02State, error::NssaError, program::Program, program_deployment_transaction::message::Message, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProgramDeploymentTransaction { + pub(crate) message: Message, +} + +impl ProgramDeploymentTransaction { + pub fn new(message: Message) -> Self { + Self { message } + } + + pub(crate) fn validate_and_produce_public_state_diff( + &self, + state: &V02State, + ) -> Result { + // TODO: remove clone + let program = Program::new(self.message.bytecode.clone())?; + if state.programs().contains_key(&program.id()) { + Err(NssaError::ProgramAlreadyExists) + } else { + Ok(program) + } + } +} diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index d6b9614..b0b8f73 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -8,7 +8,7 @@ use nssa_core::{ use sha2::{Digest, digest::FixedOutput}; use crate::{ - V01State, + V02State, error::NssaError, public_transaction::{Message, WitnessSet}, }; @@ -52,7 +52,7 @@ impl PublicTransaction { pub(crate) fn validate_and_produce_public_state_diff( &self, - state: &V01State, + state: &V02State, ) -> Result, NssaError> { let message = self.message(); let witness_set = self.witness_set(); @@ -100,9 +100,8 @@ impl PublicTransaction { }) .collect(); - // Check the `program_id` corresponds to a built-in program - // Only allowed program so far is the authenticated transfer program - let Some(program) = state.builtin_programs().get(&message.program_id) else { + // Check the `program_id` corresponds to a deployed program + let Some(program) = state.programs().get(&message.program_id) else { return Err(NssaError::InvalidInput("Unknown program".into())); }; @@ -124,7 +123,7 @@ pub mod tests { use sha2::{Digest, digest::FixedOutput}; use crate::{ - Address, PrivateKey, PublicKey, PublicTransaction, Signature, V01State, + Address, PrivateKey, PublicKey, PublicTransaction, Signature, V02State, error::NssaError, program::Program, public_transaction::{Message, WitnessSet}, @@ -138,10 +137,10 @@ pub mod tests { (key1, key2, addr1, addr2) } - fn state_for_tests() -> V01State { + fn state_for_tests() -> V02State { let (_, _, addr1, addr2) = keys_for_tests(); let initial_data = [(addr1, 10000), (addr2, 20000)]; - V01State::new_with_genesis_accounts(&initial_data, &[]) + V02State::new_with_genesis_accounts(&initial_data, &[]) } fn transaction_for_tests() -> PublicTransaction { @@ -187,12 +186,12 @@ pub mod tests { let tx = transaction_for_tests(); let expected_signer_addresses = vec![ Address::new([ - 14, 238, 36, 40, 114, 150, 186, 85, 39, 143, 30, 84, 3, 190, 1, 71, 84, 134, 99, - 102, 56, 135, 48, 48, 60, 40, 137, 190, 23, 173, 160, 101, + 208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, + 115, 84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68, ]), Address::new([ - 158, 61, 142, 101, 77, 68, 14, 149, 41, 58, 162, 220, 236, 235, 19, 120, 153, 165, - 149, 53, 233, 82, 247, 71, 6, 142, 122, 14, 227, 9, 101, 242, + 231, 174, 119, 197, 239, 26, 5, 153, 147, 68, 175, 73, 159, 199, 138, 23, 5, 57, + 141, 98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188, ]), ]; let signer_addresses = tx.signer_addresses(); diff --git a/nssa/src/signature/public_key.rs b/nssa/src/signature/public_key.rs index efa732b..dbd7d64 100644 --- a/nssa/src/signature/public_key.rs +++ b/nssa/src/signature/public_key.rs @@ -33,7 +33,7 @@ impl PublicKey { impl From<&PublicKey> for Address { fn from(key: &PublicKey) -> Self { - const PUBLIC_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.1/AccountId/Public/\x00\x00\x00\x00"; + const PUBLIC_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.2/AccountId/Public/\x00\x00\x00\x00"; let mut hasher = Sha256::new(); hasher.update(PUBLIC_ACCOUNT_ID_PREFIX); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3066809..83183f5 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1,6 +1,7 @@ use crate::{ error::NssaError, merkle_tree::MerkleTree, privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program, + program_deployment_transaction::ProgramDeploymentTransaction, public_transaction::PublicTransaction, }; use nssa_core::{ @@ -58,13 +59,13 @@ impl CommitmentSet { type NullifierSet = HashSet; -pub struct V01State { +pub struct V02State { public_state: HashMap, private_state: (CommitmentSet, NullifierSet), - builtin_programs: HashMap, + programs: HashMap, } -impl V01State { +impl V02State { pub fn new_with_genesis_accounts( initial_data: &[(Address, u128)], initial_commitments: &[nssa_core::Commitment], @@ -90,7 +91,7 @@ impl V01State { let mut this = Self { public_state, private_state: (private_state, NullifierSet::new()), - builtin_programs: HashMap::new(), + programs: HashMap::new(), }; this.insert_program(Program::authenticated_transfer_program()); @@ -100,7 +101,7 @@ impl V01State { } pub(crate) fn insert_program(&mut self, program: Program) { - self.builtin_programs.insert(program.id(), program); + self.programs.insert(program.id(), program); } pub fn transition_from_public_transaction( @@ -157,6 +158,15 @@ impl V01State { Ok(()) } + pub fn transition_from_program_deployment_transaction( + &mut self, + tx: &ProgramDeploymentTransaction, + ) -> Result<(), NssaError> { + let program = tx.validate_and_produce_public_state_diff(self)?; + self.insert_program(program); + Ok(()) + } + fn get_account_by_address_mut(&mut self, address: Address) -> &mut Account { self.public_state.entry(address).or_default() } @@ -172,8 +182,8 @@ impl V01State { self.private_state.0.get_proof_for(commitment) } - pub(crate) fn builtin_programs(&self) -> &HashMap { - &self.builtin_programs + pub(crate) fn programs(&self) -> &HashMap { + &self.programs } pub fn commitment_set_digest(&self) -> CommitmentSetDigest { @@ -215,7 +225,7 @@ impl V01State { } // TODO: Testnet only. Refactor to prevent compilation on mainnet. -impl V01State { +impl V02State { pub fn add_pinata_program(&mut self, address: Address) { self.insert_program(Program::pinata()); @@ -238,7 +248,7 @@ pub mod tests { use std::collections::HashMap; use crate::{ - Address, PublicKey, PublicTransaction, V01State, + Address, PublicKey, PublicTransaction, V02State, error::NssaError, execute_and_prove, privacy_preserving_transaction::{ @@ -309,22 +319,22 @@ pub mod tests { this }; - let state = V01State::new_with_genesis_accounts(&initial_data, &[]); + let state = V02State::new_with_genesis_accounts(&initial_data, &[]); assert_eq!(state.public_state, expected_public_state); - assert_eq!(state.builtin_programs, expected_builtin_programs); + assert_eq!(state.programs, expected_builtin_programs); } #[test] fn test_insert_program() { - let mut state = V01State::new_with_genesis_accounts(&[], &[]); + let mut state = V02State::new_with_genesis_accounts(&[], &[]); let program_to_insert = Program::simple_balance_transfer(); let program_id = program_to_insert.id(); - assert!(!state.builtin_programs.contains_key(&program_id)); + assert!(!state.programs.contains_key(&program_id)); state.insert_program(program_to_insert); - assert!(state.builtin_programs.contains_key(&program_id)); + assert!(state.programs.contains_key(&program_id)); } #[test] @@ -332,7 +342,7 @@ pub mod tests { let key = PrivateKey::try_new([1; 32]).unwrap(); let addr = Address::from(&PublicKey::new_from_private_key(&key)); let initial_data = [(addr, 100u128)]; - let state = V01State::new_with_genesis_accounts(&initial_data, &[]); + let state = V02State::new_with_genesis_accounts(&initial_data, &[]); let expected_account = state.public_state.get(&addr).unwrap(); let account = state.get_account_by_address(&addr); @@ -343,7 +353,7 @@ pub mod tests { #[test] fn test_get_account_by_address_default_account() { let addr2 = Address::new([0; 32]); - let state = V01State::new_with_genesis_accounts(&[], &[]); + let state = V02State::new_with_genesis_accounts(&[], &[]); let expected_account = Account::default(); let account = state.get_account_by_address(&addr2); @@ -353,11 +363,11 @@ pub mod tests { #[test] fn test_builtin_programs_getter() { - let state = V01State::new_with_genesis_accounts(&[], &[]); + let state = V02State::new_with_genesis_accounts(&[], &[]); - let builtin_programs = state.builtin_programs(); + let builtin_programs = state.programs(); - assert_eq!(builtin_programs, &state.builtin_programs); + assert_eq!(builtin_programs, &state.programs); } #[test] @@ -365,7 +375,7 @@ pub mod tests { let key = PrivateKey::try_new([1; 32]).unwrap(); let address = Address::from(&PublicKey::new_from_private_key(&key)); let initial_data = [(address, 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); let from = address; let to = Address::new([2; 32]); assert_eq!(state.get_account_by_address(&to), Account::default()); @@ -385,7 +395,7 @@ pub mod tests { let key = PrivateKey::try_new([1; 32]).unwrap(); let address = Address::from(&PublicKey::new_from_private_key(&key)); let initial_data = [(address, 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); let from = address; let from_key = key; let to = Address::new([2; 32]); @@ -409,7 +419,7 @@ pub mod tests { let address1 = Address::from(&PublicKey::new_from_private_key(&key1)); let address2 = Address::from(&PublicKey::new_from_private_key(&key2)); let initial_data = [(address1, 100), (address2, 200)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); let from = address2; let from_key = key2; let to = address1; @@ -432,7 +442,7 @@ pub mod tests { let key2 = PrivateKey::try_new([2; 32]).unwrap(); let address2 = Address::from(&PublicKey::new_from_private_key(&key2)); let initial_data = [(address1, 100)]; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); let address3 = Address::new([3; 32]); let balance_to_move = 5; @@ -450,7 +460,7 @@ pub mod tests { assert_eq!(state.get_account_by_address(&address3).nonce, 0); } - impl V01State { + impl V02State { pub fn force_insert_account(&mut self, address: Address, account: Account) { self.public_state.insert(address, account); } @@ -517,7 +527,7 @@ pub mod tests { fn test_program_should_fail_if_modifies_nonces() { let initial_data = [(Address::new([1; 32]), 100)]; let mut state = - V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let addresses = vec![Address::new([1; 32])]; let program_id = Program::nonce_changer_program().id(); let message = @@ -534,7 +544,7 @@ pub mod tests { fn test_program_should_fail_if_output_accounts_exceed_inputs() { let initial_data = [(Address::new([1; 32]), 100)]; let mut state = - V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let addresses = vec![Address::new([1; 32])]; let program_id = Program::extra_output_program().id(); let message = @@ -551,7 +561,7 @@ pub mod tests { fn test_program_should_fail_with_missing_output_accounts() { let initial_data = [(Address::new([1; 32]), 100)]; let mut state = - V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let addresses = vec![Address::new([1; 32]), Address::new([2; 32])]; let program_id = Program::missing_output_program().id(); let message = @@ -568,7 +578,7 @@ pub mod tests { fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() { let initial_data = [(Address::new([1; 32]), 0)]; let mut state = - V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let address = Address::new([1; 32]); let account = state.get_account_by_address(&address); // Assert the target account only differs from the default account in the program owner field @@ -590,7 +600,7 @@ pub mod tests { #[test] fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_balance() { let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let address = Address::new([255; 32]); @@ -614,7 +624,7 @@ pub mod tests { #[test] fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() { let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let address = Address::new([254; 32]); @@ -638,7 +648,7 @@ pub mod tests { #[test] fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data() { let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let address = Address::new([253; 32]); @@ -663,7 +673,7 @@ pub mod tests { fn test_program_should_fail_if_transfers_balance_from_non_owned_account() { let initial_data = [(Address::new([1; 32]), 100)]; let mut state = - V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let sender_address = Address::new([1; 32]); let receiver_address = Address::new([2; 32]); let balance_to_move: u128 = 1; @@ -690,7 +700,7 @@ pub mod tests { #[test] fn test_program_should_fail_if_modifies_data_of_non_owned_account() { let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let address = Address::new([255; 32]); @@ -715,7 +725,7 @@ pub mod tests { fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() { let initial_data = []; let mut state = - V01State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let address = Address::new([1; 32]); let program_id = Program::minter().id(); @@ -732,7 +742,7 @@ pub mod tests { #[test] fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() { let initial_data = []; - let mut state = V01State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) .with_test_programs() .with_account_owned_by_burner_program(); let program_id = Program::burner().id(); @@ -807,7 +817,7 @@ pub mod tests { sender_keys: &TestPublicKeys, recipient_keys: &TestPrivateKeys, balance_to_move: u128, - state: &V01State, + state: &V02State, ) -> PrivacyPreservingTransaction { let sender = AccountWithMetadata::new( state.get_account_by_address(&sender_keys.address()), @@ -852,7 +862,7 @@ pub mod tests { recipient_keys: &TestPrivateKeys, balance_to_move: u128, new_nonces: [Nonce; 2], - state: &V01State, + state: &V02State, ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); @@ -908,7 +918,7 @@ pub mod tests { recipient_address: &Address, balance_to_move: u128, new_nonce: Nonce, - state: &V01State, + state: &V02State, ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); @@ -956,7 +966,7 @@ pub mod tests { let sender_keys = test_public_account_keys_1(); let recipient_keys = test_private_account_keys_1(); - let mut state = V01State::new_with_genesis_accounts(&[(sender_keys.address(), 200)], &[]); + let mut state = V02State::new_with_genesis_accounts(&[(sender_keys.address(), 200)], &[]); let balance_to_move = 37; @@ -1002,7 +1012,7 @@ pub mod tests { }; let recipient_keys = test_private_account_keys_2(); - let mut state = V01State::new_with_genesis_accounts(&[], &[]) + let mut state = V02State::new_with_genesis_accounts(&[], &[]) .with_private_account(&sender_keys, &sender_private_account); let balance_to_move = 37; @@ -1068,7 +1078,7 @@ pub mod tests { }; let recipient_keys = test_public_account_keys_1(); let recipient_initial_balance = 400; - let mut state = V01State::new_with_genesis_accounts( + let mut state = V02State::new_with_genesis_accounts( &[(recipient_keys.address(), recipient_initial_balance)], &[], ) @@ -1956,7 +1966,7 @@ pub mod tests { }; let recipient_keys = test_private_account_keys_2(); - let mut state = V01State::new_with_genesis_accounts(&[], &[]) + let mut state = V02State::new_with_genesis_accounts(&[], &[]) .with_private_account(&sender_keys, &sender_private_account); let balance_to_move = 37; diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 4459371..7366471 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -88,6 +88,7 @@ impl SequencerCore { Err(TransactionMalformationError::InvalidSignature) } } + NSSATransaction::ProgramDeployment(tx) => Ok(NSSATransaction::ProgramDeployment(tx)), } } @@ -132,6 +133,12 @@ impl SequencerCore { .transition_from_privacy_preserving_transaction(tx) .inspect_err(|err| warn!("Error at transition {err:#?}"))?; } + NSSATransaction::ProgramDeployment(tx) => { + self.store + .state + .transition_from_program_deployment_transaction(tx) + .inspect_err(|err| warn!("Error at transition {err:#?}"))?; + } } Ok(tx) @@ -221,13 +228,13 @@ mod tests { fn setup_sequencer_config() -> SequencerConfig { let acc1_addr = vec![ - 14, 238, 36, 40, 114, 150, 186, 85, 39, 143, 30, 84, 3, 190, 1, 71, 84, 134, 99, 102, - 56, 135, 48, 48, 60, 40, 137, 190, 23, 173, 160, 101, + 208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, 115, + 84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68, ]; let acc2_addr = vec![ - 158, 61, 142, 101, 77, 68, 14, 149, 41, 58, 162, 220, 236, 235, 19, 120, 153, 165, 149, - 53, 233, 82, 247, 71, 6, 142, 122, 14, 227, 9, 101, 242, + 231, 174, 119, 197, 239, 26, 5, 153, 147, 68, 175, 73, 159, 199, 138, 23, 5, 57, 141, + 98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188, ]; let initial_acc1 = AccountInitialData { diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index ad2939a..4f18405 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -10,7 +10,7 @@ use crate::config::AccountInitialData; pub mod block_store; pub struct SequecerChainStore { - pub state: nssa::V01State, + pub state: nssa::V02State, pub block_store: SequecerBlockStore, } @@ -29,12 +29,12 @@ impl SequecerChainStore { .collect(); #[cfg(not(feature = "testnet"))] - let state = nssa::V01State::new_with_genesis_accounts(&init_accs, initial_commitments); + let state = nssa::V02State::new_with_genesis_accounts(&init_accs, initial_commitments); #[cfg(feature = "testnet")] let state = { let mut this = - nssa::V01State::new_with_genesis_accounts(&init_accs, initial_commitments); + nssa::V02State::new_with_genesis_accounts(&init_accs, initial_commitments); this.add_pinata_program("cafe".repeat(16).parse().unwrap()); this }; diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 2d115bb..f8c583f 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -330,13 +330,13 @@ mod tests { let tempdir = tempdir().unwrap(); let home = tempdir.path().to_path_buf(); let acc1_addr = vec![ - 14, 238, 36, 40, 114, 150, 186, 85, 39, 143, 30, 84, 3, 190, 1, 71, 84, 134, 99, 102, - 56, 135, 48, 48, 60, 40, 137, 190, 23, 173, 160, 101, + 208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, 115, + 84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68, ]; let acc2_addr = vec![ - 158, 61, 142, 101, 77, 68, 14, 149, 41, 58, 162, 220, 236, 235, 19, 120, 153, 165, 149, - 53, 233, 82, 247, 71, 6, 142, 122, 14, 227, 9, 101, 242, + 231, 174, 119, 197, 239, 26, 5, 153, 147, 68, 175, 73, 159, 199, 138, 23, 5, 57, 141, + 98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188, ]; let initial_acc1 = AccountInitialData { @@ -374,8 +374,8 @@ mod tests { let balance_to_move = 10; let tx = common::test_utils::create_transaction_native_token_transfer( [ - 14, 238, 36, 40, 114, 150, 186, 85, 39, 143, 30, 84, 3, 190, 1, 71, 84, 134, 99, - 102, 56, 135, 48, 48, 60, 40, 137, 190, 23, 173, 160, 101, + 208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, + 115, 84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68, ], 0, [2; 32], diff --git a/sequencer_runner/configs/debug/sequencer_config.json b/sequencer_runner/configs/debug/sequencer_config.json index 14de2c1..19ff458 100644 --- a/sequencer_runner/configs/debug/sequencer_config.json +++ b/sequencer_runner/configs/debug/sequencer_config.json @@ -8,11 +8,11 @@ "port": 3040, "initial_accounts": [ { - "addr": "0eee24287296ba55278f1e5403be014754866366388730303c2889be17ada065", + "addr": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44", "balance": 10000 }, { - "addr": "9e3d8e654d440e95293aa2dceceb137899a59535e952f747068e7a0ee30965f2", + "addr": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc", "balance": 20000 } ], diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index d4123d6..e07ba8e 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -77,14 +77,14 @@ mod tests { fn create_initial_accounts() -> Vec { let initial_acc1 = serde_json::from_str(r#"{ "Public": { - "address": "0eee24287296ba55278f1e5403be014754866366388730303c2889be17ada065", + "address": "d07ad2e84b27fa00c262f0a1eea0ff35ca0973547e6a106f72f193c2dc838b44", "pub_sign_key": [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] } }"#).unwrap(); let initial_acc2 = serde_json::from_str(r#"{ "Public": { - "address": "9e3d8e654d440e95293aa2dceceb137899a59535e952f747068e7a0ee30965f2", + "address": "e7ae77c5ef1a05999344af499fc78a1705398d62ed06cf2e1479f6def89a39bc", "pub_sign_key": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] } }"#).unwrap(); diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs new file mode 100644 index 0000000..9ec4b20 --- /dev/null +++ b/wallet/src/cli/account.rs @@ -0,0 +1,253 @@ +use std::str::FromStr; + +use anyhow::Result; +use clap::Subcommand; +use common::transaction::NSSATransaction; +use nssa::Address; + +use crate::{ + SubcommandReturnValue, WalletCore, cli::WalletSubcommand, helperfunctions::HumanReadableAccount, +}; + +///Represents generic chain CLI subcommand +#[derive(Subcommand, Debug, Clone)] +pub enum AccountSubcommand { + ///Get + #[command(subcommand)] + Get(GetSubcommand), + ///Fetch + #[command(subcommand)] + Fetch(FetchSubcommand), + ///Register + #[command(subcommand)] + Register(RegisterSubcommand), +} + +///Represents generic getter CLI subcommand +#[derive(Subcommand, Debug, Clone)] +pub enum GetSubcommand { + ///Get account `addr` balance + PublicAccountBalance { + #[arg(short, long)] + addr: String, + }, + ///Get account `addr` nonce + PublicAccountNonce { + #[arg(short, long)] + addr: String, + }, + ///Get account at address `addr` + PublicAccount { + #[arg(short, long)] + addr: String, + }, + ///Get private account with `addr` from storage + PrivateAccount { + #[arg(short, long)] + addr: String, + }, +} + +///Represents generic getter CLI subcommand +#[derive(Subcommand, Debug, Clone)] +pub enum FetchSubcommand { + ///Fetch transaction by `hash` + Tx { + #[arg(short, long)] + tx_hash: String, + }, + ///Claim account `acc_addr` generated in transaction `tx_hash`, using secret `sh_secret` at ciphertext id `ciph_id` + PrivateAccount { + ///tx_hash - valid 32 byte hex string + #[arg(long)] + tx_hash: String, + ///acc_addr - valid 32 byte hex string + #[arg(long)] + acc_addr: String, + ///output_id - id of the output in the transaction + #[arg(long)] + output_id: usize, + }, +} + +///Represents generic register CLI subcommand +#[derive(Subcommand, Debug, Clone)] +pub enum RegisterSubcommand { + ///Register new public account + Public {}, + ///Register new private account + Private {}, +} + +impl WalletSubcommand for GetSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + GetSubcommand::PublicAccountBalance { addr } => { + let addr = Address::from_str(&addr)?; + + let balance = wallet_core.get_account_balance(addr).await?; + println!("Accounts {addr} balance is {balance}"); + + Ok(SubcommandReturnValue::Empty) + } + GetSubcommand::PublicAccountNonce { addr } => { + let addr = Address::from_str(&addr)?; + + let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0]; + println!("Accounts {addr} nonce is {nonce}"); + + Ok(SubcommandReturnValue::Empty) + } + GetSubcommand::PublicAccount { addr } => { + let addr: Address = addr.parse()?; + let account = wallet_core.get_account_public(addr).await?; + let account_hr: HumanReadableAccount = account.clone().into(); + println!("{}", serde_json::to_string(&account_hr).unwrap()); + + Ok(SubcommandReturnValue::Account(account)) + } + GetSubcommand::PrivateAccount { addr } => { + let addr: Address = addr.parse()?; + if let Some(account) = wallet_core.get_account_private(&addr) { + println!("{}", serde_json::to_string(&account).unwrap()); + } else { + println!("Private account not found."); + } + Ok(SubcommandReturnValue::Empty) + } + } + } +} + +impl WalletSubcommand for FetchSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + FetchSubcommand::Tx { tx_hash } => { + let tx_obj = wallet_core + .sequencer_client + .get_transaction_by_hash(tx_hash) + .await?; + + println!("Transaction object {tx_obj:#?}"); + + Ok(SubcommandReturnValue::Empty) + } + FetchSubcommand::PrivateAccount { + tx_hash, + acc_addr, + output_id: ciph_id, + } => { + let acc_addr: Address = acc_addr.parse().unwrap(); + + let account_key_chain = wallet_core + .storage + .user_data + .user_private_accounts + .get(&acc_addr); + + let Some((account_key_chain, _)) = account_key_chain else { + anyhow::bail!("Account not found"); + }; + + let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let to_ebc = tx.message.encrypted_private_post_states[ciph_id].clone(); + let to_comm = tx.message.new_commitments[ciph_id].clone(); + let shared_secret = + account_key_chain.calculate_shared_secret_receiver(to_ebc.epk); + + let res_acc_to = nssa_core::EncryptionScheme::decrypt( + &to_ebc.ciphertext, + &shared_secret, + &to_comm, + ciph_id as u32, + ) + .unwrap(); + + println!("RES acc to {res_acc_to:#?}"); + + println!("Transaction data is {:?}", tx.message); + + wallet_core + .storage + .insert_private_account_data(acc_addr, res_acc_to); + } + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::Empty) + } + } + } +} + +impl WalletSubcommand for RegisterSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + RegisterSubcommand::Public {} => { + let addr = wallet_core.create_new_account_public(); + + println!("Generated new account with addr {addr}"); + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::RegisterAccount { addr }) + } + RegisterSubcommand::Private {} => { + let addr = wallet_core.create_new_account_private(); + + let (key, _) = wallet_core + .storage + .user_data + .get_private_account(&addr) + .unwrap(); + + println!("Generated new account with addr {addr}"); + println!("With npk {}", hex::encode(&key.nullifer_public_key)); + println!( + "With ipk {}", + hex::encode(key.incoming_viewing_public_key.to_bytes()) + ); + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::RegisterAccount { addr }) + } + } + } +} + +impl WalletSubcommand for AccountSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + AccountSubcommand::Get(get_subcommand) => { + get_subcommand.handle_subcommand(wallet_core).await + } + AccountSubcommand::Fetch(fetch_subcommand) => { + fetch_subcommand.handle_subcommand(wallet_core).await + } + AccountSubcommand::Register(register_subcommand) => { + register_subcommand.handle_subcommand(wallet_core).await + } + } + } +} diff --git a/wallet/src/cli/chain.rs b/wallet/src/cli/chain.rs new file mode 100644 index 0000000..4db18fc --- /dev/null +++ b/wallet/src/cli/chain.rs @@ -0,0 +1,47 @@ +use anyhow::Result; +use clap::Subcommand; + +use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand}; + +///Represents generic chain CLI subcommand +#[derive(Subcommand, Debug, Clone)] +pub enum ChainSubcommand { + GetLatestBlockId {}, + GetBlockAtId { + #[arg(short, long)] + id: u64, + }, + GetTransactionAtHash { + #[arg(short, long)] + hash: String, + }, +} + +impl WalletSubcommand for ChainSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + ChainSubcommand::GetLatestBlockId {} => { + let latest_block_res = wallet_core.sequencer_client.get_last_block().await?; + + println!("Last block id is {}", latest_block_res.last_block); + } + ChainSubcommand::GetBlockAtId { id } => { + let block_res = wallet_core.sequencer_client.get_block(id).await?; + + println!("Last block id is {:#?}", block_res.block); + } + ChainSubcommand::GetTransactionAtHash { hash } => { + let tx_res = wallet_core + .sequencer_client + .get_transaction_by_hash(hash) + .await?; + + println!("Last block id is {:#?}", tx_res.transaction); + } + } + Ok(SubcommandReturnValue::Empty) + } +} diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index 5b277e0..3aa1b7f 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -2,6 +2,10 @@ use anyhow::Result; use crate::{SubcommandReturnValue, WalletCore}; +pub mod account; +pub mod chain; +pub mod native_token_transfer_program; +pub mod pinata_program; pub mod token_program; pub(crate) trait WalletSubcommand { diff --git a/wallet/src/cli/native_token_transfer_program.rs b/wallet/src/cli/native_token_transfer_program.rs new file mode 100644 index 0000000..666ea68 --- /dev/null +++ b/wallet/src/cli/native_token_transfer_program.rs @@ -0,0 +1,362 @@ +use anyhow::Result; +use clap::Subcommand; +use common::transaction::NSSATransaction; +use nssa::Address; + +use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand}; + +///Represents generic CLI subcommand for a wallet working with native token transfer program +#[derive(Subcommand, Debug, Clone)] +pub enum NativeTokenTransferProgramSubcommand { + ///Send native token transfer from `from` to `to` for `amount` + /// + /// Public operation + Public { + ///from - valid 32 byte hex string + #[arg(long)] + from: String, + ///to - valid 32 byte hex string + #[arg(long)] + to: String, + ///amount - amount of balance to move + #[arg(long)] + amount: u128, + }, + ///Private execution + #[command(subcommand)] + Private(NativeTokenTransferProgramSubcommandPrivate), + ///Send native token transfer from `from` to `to` for `amount` + /// + /// Deshielded operation + Deshielded { + ///from - valid 32 byte hex string + #[arg(long)] + from: String, + ///to - valid 32 byte hex string + #[arg(long)] + to: String, + ///amount - amount of balance to move + #[arg(long)] + amount: u128, + }, + ///Shielded execution + #[command(subcommand)] + Shielded(NativeTokenTransferProgramSubcommandShielded), +} + +///Represents generic shielded CLI subcommand for a wallet working with native token transfer program +#[derive(Subcommand, Debug, Clone)] +pub enum NativeTokenTransferProgramSubcommandShielded { + ///Send native token transfer from `from` to `to` for `amount` + /// + /// Shielded operation + ShieldedOwned { + ///from - valid 32 byte hex string + #[arg(long)] + from: String, + ///to - valid 32 byte hex string + #[arg(long)] + to: String, + ///amount - amount of balance to move + #[arg(long)] + amount: u128, + }, + ///Send native token transfer from `from` to `to` for `amount` + /// + /// Shielded operation + ShieldedForeign { + ///from - valid 32 byte hex string + #[arg(long)] + from: String, + ///to_npk - valid 32 byte hex string + #[arg(long)] + to_npk: String, + ///to_ipk - valid 33 byte hex string + #[arg(long)] + to_ipk: String, + ///amount - amount of balance to move + #[arg(long)] + amount: u128, + }, +} + +///Represents generic private CLI subcommand for a wallet working with native token transfer program +#[derive(Subcommand, Debug, Clone)] +pub enum NativeTokenTransferProgramSubcommandPrivate { + ///Send native token transfer from `from` to `to` for `amount` + /// + /// Private operation + PrivateOwned { + ///from - valid 32 byte hex string + #[arg(long)] + from: String, + ///to - valid 32 byte hex string + #[arg(long)] + to: String, + ///amount - amount of balance to move + #[arg(long)] + amount: u128, + }, + ///Send native token transfer from `from` to `to` for `amount` + /// + /// Private operation + PrivateForeign { + ///from - valid 32 byte hex string + #[arg(long)] + from: String, + ///to_npk - valid 32 byte hex string + #[arg(long)] + to_npk: String, + ///to_ipk - valid 33 byte hex string + #[arg(long)] + to_ipk: String, + ///amount - amount of balance to move + #[arg(long)] + amount: u128, + }, +} + +impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + NativeTokenTransferProgramSubcommandPrivate::PrivateOwned { from, to, amount } => { + let from: Address = from.parse().unwrap(); + let to: Address = to.parse().unwrap(); + + let to_initialization = wallet_core.check_private_account_initialized(&to).await?; + + let (res, [secret_from, secret_to]) = if let Some(to_proof) = to_initialization { + wallet_core + .send_private_native_token_transfer_owned_account_already_initialized( + from, to, amount, to_proof, + ) + .await? + } else { + wallet_core + .send_private_native_token_transfer_owned_account_not_initialized( + from, to, amount, + ) + .await? + }; + + println!("Results of tx send is {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_from, from), (secret_to, to)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + NativeTokenTransferProgramSubcommandPrivate::PrivateForeign { + from, + to_npk, + to_ipk, + amount, + } => { + let from: Address = from.parse().unwrap(); + let to_npk_res = hex::decode(to_npk)?; + let mut to_npk = [0; 32]; + to_npk.copy_from_slice(&to_npk_res); + let to_npk = nssa_core::NullifierPublicKey(to_npk); + + let to_ipk_res = hex::decode(to_ipk)?; + let mut to_ipk = [0u8; 33]; + to_ipk.copy_from_slice(&to_ipk_res); + let to_ipk = + nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec()); + + let (res, [secret_from, _]) = wallet_core + .send_private_native_token_transfer_outer_account(from, to_npk, to_ipk, amount) + .await?; + + println!("Results of tx send is {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_from, from)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + } + } +} + +impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + NativeTokenTransferProgramSubcommandShielded::ShieldedOwned { from, to, amount } => { + let from: Address = from.parse().unwrap(); + let to: Address = to.parse().unwrap(); + + let to_initialization = wallet_core.check_private_account_initialized(&to).await?; + + let (res, secret) = if let Some(to_proof) = to_initialization { + wallet_core + .send_shielded_native_token_transfer_already_initialized( + from, to, amount, to_proof, + ) + .await? + } else { + wallet_core + .send_shielded_native_token_transfer_not_initialized(from, to, amount) + .await? + }; + + println!("Results of tx send is {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, to)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + NativeTokenTransferProgramSubcommandShielded::ShieldedForeign { + from, + to_npk, + to_ipk, + amount, + } => { + let from: Address = from.parse().unwrap(); + + let to_npk_res = hex::decode(to_npk)?; + let mut to_npk = [0; 32]; + to_npk.copy_from_slice(&to_npk_res); + let to_npk = nssa_core::NullifierPublicKey(to_npk); + + let to_ipk_res = hex::decode(to_ipk)?; + let mut to_ipk = [0u8; 33]; + to_ipk.copy_from_slice(&to_ipk_res); + let to_ipk = + nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec()); + + let (res, _) = wallet_core + .send_shielded_native_token_transfer_outer_account(from, to_npk, to_ipk, amount) + .await?; + + println!("Results of tx send is {res:#?}"); + + let tx_hash = res.tx_hash; + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + } + } +} + +impl WalletSubcommand for NativeTokenTransferProgramSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + NativeTokenTransferProgramSubcommand::Private(private_subcommand) => { + private_subcommand.handle_subcommand(wallet_core).await + } + NativeTokenTransferProgramSubcommand::Shielded(shielded_subcommand) => { + shielded_subcommand.handle_subcommand(wallet_core).await + } + NativeTokenTransferProgramSubcommand::Deshielded { from, to, amount } => { + let from: Address = from.parse().unwrap(); + let to: Address = to.parse().unwrap(); + + let (res, secret) = wallet_core + .send_deshielded_native_token_transfer(from, to, amount) + .await?; + + println!("Results of tx send is {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, from)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + NativeTokenTransferProgramSubcommand::Public { from, to, amount } => { + let from: Address = from.parse().unwrap(); + let to: Address = to.parse().unwrap(); + + let res = wallet_core + .send_public_native_token_transfer(from, to, amount) + .await?; + + println!("Results of tx send is {res:#?}"); + + let transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?; + + println!("Transaction data is {transfer_tx:?}"); + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::Empty) + } + } + } +} diff --git a/wallet/src/cli/pinata_program.rs b/wallet/src/cli/pinata_program.rs new file mode 100644 index 0000000..75d3d6a --- /dev/null +++ b/wallet/src/cli/pinata_program.rs @@ -0,0 +1,158 @@ +use anyhow::Result; +use clap::Subcommand; +use common::transaction::NSSATransaction; +use log::info; + +use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand}; + +///Represents generic CLI subcommand for a wallet working with pinata program +#[derive(Subcommand, Debug, Clone)] +pub enum PinataProgramSubcommand { + ///Public execution + #[command(subcommand)] + Public(PinataProgramSubcommandPublic), + ///Private execution + #[command(subcommand)] + Private(PinataProgramSubcommandPrivate), +} + +///Represents generic public CLI subcommand for a wallet working with pinata program +#[derive(Subcommand, Debug, Clone)] +pub enum PinataProgramSubcommandPublic { + // TODO: Testnet only. Refactor to prevent compilation on mainnet. + // Claim piñata prize + Claim { + ///pinata_addr - valid 32 byte hex string + #[arg(long)] + pinata_addr: String, + ///winner_addr - valid 32 byte hex string + #[arg(long)] + winner_addr: String, + ///solution - solution to pinata challenge + #[arg(long)] + solution: u128, + }, +} + +///Represents generic private CLI subcommand for a wallet working with pinata program +#[derive(Subcommand, Debug, Clone)] +pub enum PinataProgramSubcommandPrivate { + // TODO: Testnet only. Refactor to prevent compilation on mainnet. + // Claim piñata prize + ClaimPrivateOwned { + ///pinata_addr - valid 32 byte hex string + #[arg(long)] + pinata_addr: String, + ///winner_addr - valid 32 byte hex string + #[arg(long)] + winner_addr: String, + ///solution - solution to pinata challenge + #[arg(long)] + solution: u128, + }, +} + +impl WalletSubcommand for PinataProgramSubcommandPublic { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + PinataProgramSubcommandPublic::Claim { + pinata_addr, + winner_addr, + solution, + } => { + let res = wallet_core + .claim_pinata( + pinata_addr.parse().unwrap(), + winner_addr.parse().unwrap(), + solution, + ) + .await?; + info!("Results of tx send is {res:#?}"); + + Ok(SubcommandReturnValue::Empty) + } + } + } +} + +impl WalletSubcommand for PinataProgramSubcommandPrivate { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + PinataProgramSubcommandPrivate::ClaimPrivateOwned { + pinata_addr, + winner_addr, + solution, + } => { + let pinata_addr = pinata_addr.parse().unwrap(); + let winner_addr = winner_addr.parse().unwrap(); + + let winner_initialization = wallet_core + .check_private_account_initialized(&winner_addr) + .await?; + + let (res, [secret_winner]) = if let Some(winner_proof) = winner_initialization { + wallet_core + .claim_pinata_private_owned_account_already_initialized( + pinata_addr, + winner_addr, + solution, + winner_proof, + ) + .await? + } else { + wallet_core + .claim_pinata_private_owned_account_not_initialized( + pinata_addr, + winner_addr, + solution, + ) + .await? + }; + + info!("Results of tx send is {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_winner, winner_addr)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_accounts().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + } + } +} + +impl WalletSubcommand for PinataProgramSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + PinataProgramSubcommand::Private(private_subcommand) => { + private_subcommand.handle_subcommand(wallet_core).await + } + PinataProgramSubcommand::Public(public_subcommand) => { + public_subcommand.handle_subcommand(wallet_core).await + } + } + } +} diff --git a/wallet/src/cli/token_program.rs b/wallet/src/cli/token_program.rs index 02b25b1..25de77d 100644 --- a/wallet/src/cli/token_program.rs +++ b/wallet/src/cli/token_program.rs @@ -221,7 +221,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_accounts()?; + let path = wallet_core.store_persistent_accounts().await?; println!("Stored persistent accounts at {path:#?}"); @@ -278,7 +278,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_accounts()?; + let path = wallet_core.store_persistent_accounts().await?; println!("Stored persistent accounts at {path:#?}"); @@ -328,7 +328,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_accounts()?; + let path = wallet_core.store_persistent_accounts().await?; println!("Stored persistent accounts at {path:#?}"); @@ -376,7 +376,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { )?; } - let path = wallet_core.store_persistent_accounts()?; + let path = wallet_core.store_persistent_accounts().await?; println!("Stored persistent accounts at {path:#?}"); @@ -431,7 +431,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { println!("Transaction data is {:?}", tx.message); } - let path = wallet_core.store_persistent_accounts()?; + let path = wallet_core.store_persistent_accounts().await?; println!("Stored persistent accounts at {path:#?}"); @@ -485,7 +485,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_accounts()?; + let path = wallet_core.store_persistent_accounts().await?; println!("Stored persistent accounts at {path:#?}"); diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 20f4eec..a67b8ec 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -1,7 +1,8 @@ use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use nssa_core::account::Nonce; use rand::{RngCore, rngs::OsRng}; -use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr}; +use std::{path::PathBuf, str::FromStr}; +use tokio::io::AsyncReadExt; use anyhow::Result; use key_protocol::key_protocol_core::NSSAUserData; @@ -22,25 +23,25 @@ pub fn get_home() -> Result { } /// Fetch config from `NSSA_WALLET_HOME_DIR` -pub fn fetch_config() -> Result { +pub async fn fetch_config() -> Result { let config_home = get_home()?; - let file = File::open(config_home.join("wallet_config.json"))?; - let reader = BufReader::new(file); + let config_contents = tokio::fs::read(config_home.join("wallet_config.json")).await?; - Ok(serde_json::from_reader(reader)?) + Ok(serde_json::from_slice(&config_contents)?) } /// Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` /// /// If file not present, it is considered as empty list of persistent accounts -pub fn fetch_persistent_accounts() -> Result> { +pub async fn fetch_persistent_accounts() -> Result> { let home = get_home()?; let accs_path = home.join("curr_accounts.json"); + let mut persistent_accounts_content = vec![]; - match File::open(accs_path) { - Ok(file) => { - let reader = BufReader::new(file); - Ok(serde_json::from_reader(reader)?) + match tokio::fs::File::open(accs_path).await { + Ok(mut file) => { + file.read_to_end(&mut persistent_accounts_content).await?; + Ok(serde_json::from_slice(&persistent_accounts_content)?) } Err(err) => match err.kind() { std::io::ErrorKind::NotFound => Ok(vec![]), diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index fd9f4c0..5adfa39 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,7 +1,8 @@ -use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use common::{ + block::HashableBlockData, sequencer_client::SequencerClient, transaction::{EncodedTransaction, NSSATransaction}, }; @@ -10,17 +11,24 @@ use anyhow::Result; use chain_storage::WalletChainStore; use config::WalletConfig; use log::info; -use nssa::{Account, Address, program::Program}; +use nssa::{ + Account, Address, privacy_preserving_transaction::message::EncryptedAccountData, + program::Program, +}; use clap::{Parser, Subcommand}; use nssa_core::{Commitment, MembershipProof}; +use tokio::io::AsyncWriteExt; -use crate::cli::WalletSubcommand; +use crate::cli::{ + WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand, + native_token_transfer_program::NativeTokenTransferProgramSubcommand, + pinata_program::PinataProgramSubcommand, +}; use crate::{ cli::token_program::TokenProgramSubcommand, helperfunctions::{ - HumanReadableAccount, fetch_config, fetch_persistent_accounts, get_home, - produce_data_for_storage, + fetch_config, fetch_persistent_accounts, get_home, produce_data_for_storage, }, poller::TxPoller, }; @@ -43,13 +51,13 @@ pub struct WalletCore { } impl WalletCore { - pub fn start_from_config_update_chain(config: WalletConfig) -> Result { + pub async fn start_from_config_update_chain(config: WalletConfig) -> Result { let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); let tx_poller = TxPoller::new(config.clone(), client.clone()); let mut storage = WalletChainStore::new(config)?; - let persistent_accounts = fetch_persistent_accounts()?; + let persistent_accounts = fetch_persistent_accounts().await?; for pers_acc_data in persistent_accounts { storage.insert_account_data(pers_acc_data); } @@ -62,15 +70,15 @@ impl WalletCore { } ///Store persistent accounts at home - pub fn store_persistent_accounts(&self) -> Result { + pub async fn store_persistent_accounts(&self) -> Result { let home = get_home()?; let accs_path = home.join("curr_accounts.json"); let data = produce_data_for_storage(&self.storage.user_data); let accs = serde_json::to_vec_pretty(&data)?; - let mut accs_file = File::create(accs_path.as_path())?; - accs_file.write_all(&accs)?; + let mut accs_file = tokio::fs::File::create(accs_path.as_path()).await?; + accs_file.write_all(&accs).await?; info!("Stored accounts data at {accs_path:#?}"); @@ -182,179 +190,37 @@ impl WalletCore { #[derive(Subcommand, Debug, Clone)] #[clap(about)] pub enum Command { - ///Send native token transfer from `from` to `to` for `amount` - /// - /// Public operation - SendNativeTokenTransferPublic { - ///from - valid 32 byte hex string - #[arg(long)] - from: String, - ///to - valid 32 byte hex string - #[arg(long)] - to: String, - ///amount - amount of balance to move - #[arg(long)] - amount: u128, - }, - ///Send native token transfer from `from` to `to` for `amount` - /// - /// Private operation - SendNativeTokenTransferPrivateOwnedAccount { - ///from - valid 32 byte hex string - #[arg(long)] - from: String, - ///to - valid 32 byte hex string - #[arg(long)] - to: String, - ///amount - amount of balance to move - #[arg(long)] - amount: u128, - }, - ///Send native token transfer from `from` to `to` for `amount` - /// - /// Private operation - SendNativeTokenTransferPrivateForeignAccount { - ///from - valid 32 byte hex string - #[arg(long)] - from: String, - ///to_npk - valid 32 byte hex string - #[arg(long)] - to_npk: String, - ///to_ipk - valid 33 byte hex string - #[arg(long)] - to_ipk: String, - ///amount - amount of balance to move - #[arg(long)] - amount: u128, - }, - ///Send native token transfer from `from` to `to` for `amount` - /// - /// Deshielded operation - SendNativeTokenTransferDeshielded { - ///from - valid 32 byte hex string - #[arg(long)] - from: String, - ///to - valid 32 byte hex string - #[arg(long)] - to: String, - ///amount - amount of balance to move - #[arg(long)] - amount: u128, - }, - ///Send native token transfer from `from` to `to` for `amount` - /// - /// Shielded operation - SendNativeTokenTransferShielded { - ///from - valid 32 byte hex string - #[arg(long)] - from: String, - ///to - valid 32 byte hex string - #[arg(long)] - to: String, - ///amount - amount of balance to move - #[arg(long)] - amount: u128, - }, - ///Send native token transfer from `from` to `to` for `amount` - /// - /// Shielded operation - SendNativeTokenTransferShieldedForeignAccount { - ///from - valid 32 byte hex string - #[arg(long)] - from: String, - ///to_npk - valid 32 byte hex string - #[arg(long)] - to_npk: String, - ///to_ipk - valid 33 byte hex string - #[arg(long)] - to_ipk: String, - ///amount - amount of balance to move - #[arg(long)] - amount: u128, - }, - ///Claim account `acc_addr` generated in transaction `tx_hash`, using secret `sh_secret` at ciphertext id `ciph_id` - FetchPrivateAccount { - ///tx_hash - valid 32 byte hex string - #[arg(long)] - tx_hash: String, - ///acc_addr - valid 32 byte hex string - #[arg(long)] - acc_addr: String, - ///output_id - id of the output in the transaction - #[arg(long)] - output_id: usize, - }, - ///Get private account with `addr` from storage - GetPrivateAccount { - #[arg(short, long)] - addr: String, - }, - ///Register new public account - RegisterAccountPublic {}, - ///Register new private account - RegisterAccountPrivate {}, - ///Fetch transaction by `hash` - FetchTx { - #[arg(short, long)] - tx_hash: String, - }, - ///Get account `addr` balance - GetPublicAccountBalance { - #[arg(short, long)] - addr: String, - }, - ///Get account `addr` nonce - GetPublicAccountNonce { - #[arg(short, long)] - addr: String, - }, - ///Get account at address `addr` - GetPublicAccount { - #[arg(short, long)] - addr: String, - }, - // TODO: Testnet only. Refactor to prevent compilation on mainnet. - // Claim piñata prize - ClaimPinata { - ///pinata_addr - valid 32 byte hex string - #[arg(long)] - pinata_addr: String, - ///winner_addr - valid 32 byte hex string - #[arg(long)] - winner_addr: String, - ///solution - solution to pinata challenge - #[arg(long)] - solution: u128, - }, + ///Transfer command + #[command(subcommand)] + Transfer(NativeTokenTransferProgramSubcommand), + ///Chain command + #[command(subcommand)] + Chain(ChainSubcommand), + ///Chain command + #[command(subcommand)] + Account(AccountSubcommand), + ///Pinata command + #[command(subcommand)] + PinataProgram(PinataProgramSubcommand), + ///Token command + #[command(subcommand)] + TokenProgram(TokenProgramSubcommand), AuthenticatedTransferInitializePublicAccount {}, // Check the wallet can connect to the node and builtin local programs // match the remote versions CheckHealth {}, - // TODO: Testnet only. Refactor to prevent compilation on mainnet. - // Claim piñata prize - ClaimPinataPrivateReceiverOwned { - ///pinata_addr - valid 32 byte hex string - #[arg(long)] - pinata_addr: String, - ///winner_addr - valid 32 byte hex string - #[arg(long)] - winner_addr: String, - ///solution - solution to pinata challenge - #[arg(long)] - solution: u128, - }, - ///Token command - #[command(subcommand)] - TokenProgram(TokenProgramSubcommand), } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config #[derive(Parser, Debug)] #[clap(version, about)] pub struct Args { + /// Continious run flag + #[arg(short, long)] + pub continious_run: bool, /// Wallet command #[command(subcommand)] - pub command: Command, + pub command: Option, } #[derive(Debug, Clone)] @@ -366,328 +232,27 @@ pub enum SubcommandReturnValue { } pub async fn execute_subcommand(command: Command) -> Result { - let wallet_config = fetch_config()?; - let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config)?; + let wallet_config = fetch_config().await?; + let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; let subcommand_ret = match command { - Command::SendNativeTokenTransferPublic { from, to, amount } => { - let from: Address = from.parse().unwrap(); - let to: Address = to.parse().unwrap(); - - let res = wallet_core - .send_public_native_token_transfer(from, to, amount) - .await?; - - println!("Results of tx send is {res:#?}"); - - let transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?; - - println!("Transaction data is {transfer_tx:?}"); - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::Empty + Command::Transfer(transfer_subcommand) => { + transfer_subcommand + .handle_subcommand(&mut wallet_core) + .await? } - Command::SendNativeTokenTransferPrivateOwnedAccount { from, to, amount } => { - let from: Address = from.parse().unwrap(); - let to: Address = to.parse().unwrap(); - - let (res, [secret_from, secret_to]) = wallet_core - .send_private_native_token_transfer_owned_account(from, to, amount) - .await?; - - println!("Results of tx send is {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_from, from), (secret_to, to)]; - - wallet_core - .decode_insert_privacy_preserving_transaction_results(tx, &acc_decode_data)?; - } - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } + Command::Chain(chain_subcommand) => { + chain_subcommand.handle_subcommand(&mut wallet_core).await? } - Command::SendNativeTokenTransferPrivateForeignAccount { - from, - to_npk, - to_ipk, - amount, - } => { - let from: Address = from.parse().unwrap(); - let to_npk_res = hex::decode(to_npk)?; - let mut to_npk = [0; 32]; - to_npk.copy_from_slice(&to_npk_res); - let to_npk = nssa_core::NullifierPublicKey(to_npk); - - let to_ipk_res = hex::decode(to_ipk)?; - let mut to_ipk = [0u8; 33]; - to_ipk.copy_from_slice(&to_ipk_res); - let to_ipk = - nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec()); - - let (res, [secret_from, _]) = wallet_core - .send_private_native_token_transfer_outer_account(from, to_npk, to_ipk, amount) - .await?; - - println!("Results of tx send is {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_from, from)]; - - wallet_core - .decode_insert_privacy_preserving_transaction_results(tx, &acc_decode_data)?; - } - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } + Command::Account(account_subcommand) => { + account_subcommand + .handle_subcommand(&mut wallet_core) + .await? } - Command::SendNativeTokenTransferDeshielded { from, to, amount } => { - let from: Address = from.parse().unwrap(); - let to: Address = to.parse().unwrap(); - - let (res, secret) = wallet_core - .send_deshielded_native_token_transfer(from, to, amount) - .await?; - - println!("Results of tx send is {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, from)]; - - wallet_core - .decode_insert_privacy_preserving_transaction_results(tx, &acc_decode_data)?; - } - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } - } - Command::SendNativeTokenTransferShielded { from, to, amount } => { - let from: Address = from.parse().unwrap(); - let to: Address = to.parse().unwrap(); - - let (res, secret) = wallet_core - .send_shielded_native_token_transfer(from, to, amount) - .await?; - - println!("Results of tx send is {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, to)]; - - wallet_core - .decode_insert_privacy_preserving_transaction_results(tx, &acc_decode_data)?; - } - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } - } - Command::SendNativeTokenTransferShieldedForeignAccount { - from, - to_npk, - to_ipk, - amount, - } => { - let from: Address = from.parse().unwrap(); - - let to_npk_res = hex::decode(to_npk)?; - let mut to_npk = [0; 32]; - to_npk.copy_from_slice(&to_npk_res); - let to_npk = nssa_core::NullifierPublicKey(to_npk); - - let to_ipk_res = hex::decode(to_ipk)?; - let mut to_ipk = [0u8; 33]; - to_ipk.copy_from_slice(&to_ipk_res); - let to_ipk = - nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec()); - - let (res, _) = wallet_core - .send_shielded_native_token_transfer_outer_account(from, to_npk, to_ipk, amount) - .await?; - - println!("Results of tx send is {res:#?}"); - - let tx_hash = res.tx_hash; - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } - } - Command::FetchPrivateAccount { - tx_hash, - acc_addr, - output_id: ciph_id, - } => { - let acc_addr: Address = acc_addr.parse().unwrap(); - - let account_key_chain = wallet_core - .storage - .user_data - .user_private_accounts - .get(&acc_addr); - - let Some((account_key_chain, _)) = account_key_chain else { - anyhow::bail!("Account not found"); - }; - - let transfer_tx = wallet_core.poll_native_token_transfer(tx_hash).await?; - - if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let to_ebc = tx.message.encrypted_private_post_states[ciph_id].clone(); - let to_comm = tx.message.new_commitments[ciph_id].clone(); - let shared_secret = account_key_chain.calculate_shared_secret_receiver(to_ebc.epk); - - let res_acc_to = nssa_core::EncryptionScheme::decrypt( - &to_ebc.ciphertext, - &shared_secret, - &to_comm, - ciph_id as u32, - ) - .unwrap(); - - println!("RES acc to {res_acc_to:#?}"); - - println!("Transaction data is {:?}", tx.message); - - wallet_core - .storage - .insert_private_account_data(acc_addr, res_acc_to); - } - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::Empty - } - Command::RegisterAccountPublic {} => { - let addr = wallet_core.create_new_account_public(); - - println!("Generated new account with addr {addr}"); - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::RegisterAccount { addr } - } - Command::RegisterAccountPrivate {} => { - let addr = wallet_core.create_new_account_private(); - - let (key, _) = wallet_core - .storage - .user_data - .get_private_account(&addr) - .unwrap(); - - println!("Generated new account with addr {addr}"); - println!("With npk {}", hex::encode(&key.nullifer_public_key)); - println!( - "With ipk {}", - hex::encode(key.incoming_viewing_public_key.to_bytes()) - ); - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::RegisterAccount { addr } - } - Command::FetchTx { tx_hash } => { - let tx_obj = wallet_core - .sequencer_client - .get_transaction_by_hash(tx_hash) - .await?; - - println!("Transaction object {tx_obj:#?}"); - - SubcommandReturnValue::Empty - } - Command::GetPublicAccountBalance { addr } => { - let addr = Address::from_str(&addr)?; - - let balance = wallet_core.get_account_balance(addr).await?; - println!("Accounts {addr} balance is {balance}"); - - SubcommandReturnValue::Empty - } - Command::GetPublicAccountNonce { addr } => { - let addr = Address::from_str(&addr)?; - - let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0]; - println!("Accounts {addr} nonce is {nonce}"); - - SubcommandReturnValue::Empty - } - Command::GetPublicAccount { addr } => { - let addr: Address = addr.parse()?; - let account = wallet_core.get_account_public(addr).await?; - let account_hr: HumanReadableAccount = account.clone().into(); - println!("{}", serde_json::to_string(&account_hr).unwrap()); - - SubcommandReturnValue::Account(account) - } - Command::GetPrivateAccount { addr } => { - let addr: Address = addr.parse()?; - if let Some(account) = wallet_core.get_account_private(&addr) { - let account_hr: HumanReadableAccount = account.into(); - println!("{}", serde_json::to_string(&account_hr).unwrap()); - } else { - println!("Private account not found."); - } - SubcommandReturnValue::Empty - } - Command::ClaimPinata { - pinata_addr, - winner_addr, - solution, - } => { - let res = wallet_core - .claim_pinata( - pinata_addr.parse().unwrap(), - winner_addr.parse().unwrap(), - solution, - ) - .await?; - info!("Results of tx send is {res:#?}"); - - SubcommandReturnValue::Empty + Command::PinataProgram(pinata_subcommand) => { + pinata_subcommand + .handle_subcommand(&mut wallet_core) + .await? } Command::CheckHealth {} => { let remote_program_ids = wallet_core @@ -719,63 +284,12 @@ pub async fn execute_subcommand(command: Command) -> Result { - let pinata_addr = pinata_addr.parse().unwrap(); - let winner_addr = winner_addr.parse().unwrap(); - - let winner_initialization = wallet_core - .check_private_account_initialized(&winner_addr) - .await?; - - let (res, [secret_winner]) = if let Some(winner_proof) = winner_initialization { - wallet_core - .claim_pinata_private_owned_account_already_initialized( - pinata_addr, - winner_addr, - solution, - winner_proof, - ) - .await? - } else { - wallet_core - .claim_pinata_private_owned_account_not_initialized( - pinata_addr, - winner_addr, - solution, - ) - .await? - }; - - info!("Results of tx send is {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_winner, winner_addr)]; - - wallet_core - .decode_insert_privacy_preserving_transaction_results(tx, &acc_decode_data)?; - } - - let path = wallet_core.store_persistent_accounts()?; - - println!("Stored persistent accounts at {path:#?}"); - - SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } - } Command::AuthenticatedTransferInitializePublicAccount {} => { let addr = wallet_core.create_new_account_public(); println!("Generated new account with addr {addr}"); - let path = wallet_core.store_persistent_accounts()?; + let path = wallet_core.store_persistent_accounts().await?; println!("Stored persistent accounts at {path:#?}"); @@ -796,3 +310,88 @@ pub async fn execute_subcommand(command: Command) -> Result Result<()> { + let config = fetch_config().await?; + let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); + let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?; + + let mut latest_block_num = seq_client.get_last_block().await?.last_block; + let mut curr_last_block = latest_block_num; + + loop { + for block_id in curr_last_block..(latest_block_num + 1) { + let block = borsh::from_slice::( + &seq_client.get_block(block_id).await?.block, + )?; + + for tx in block.transactions { + let nssa_tx = NSSATransaction::try_from(&tx)?; + + if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx { + let mut affected_accounts = vec![]; + + for (acc_addr, (key_chain, _)) in + &wallet_core.storage.user_data.user_private_accounts + { + let view_tag = EncryptedAccountData::compute_view_tag( + key_chain.nullifer_public_key.clone(), + key_chain.incoming_viewing_public_key.clone(), + ); + + for (ciph_id, encrypted_data) in tx + .message() + .encrypted_private_post_states + .iter() + .enumerate() + { + if encrypted_data.view_tag == view_tag { + let ciphertext = &encrypted_data.ciphertext; + let commitment = &tx.message.new_commitments[ciph_id]; + let shared_secret = key_chain + .calculate_shared_secret_receiver(encrypted_data.epk.clone()); + + let res_acc = nssa_core::EncryptionScheme::decrypt( + ciphertext, + &shared_secret, + commitment, + ciph_id as u32, + ); + + if let Some(res_acc) = res_acc { + println!( + "Received new account for addr {acc_addr:#?} with account object {res_acc:#?}" + ); + + affected_accounts.push((*acc_addr, res_acc)); + } + } + } + } + + for (affected_addr, new_acc) in affected_accounts { + wallet_core + .storage + .insert_private_account_data(affected_addr, new_acc); + } + } + } + + wallet_core.store_persistent_accounts().await?; + + println!( + "Block at id {block_id} with timestamp {} parsed", + block.timestamp + ); + } + + curr_last_block = latest_block_num + 1; + + tokio::time::sleep(std::time::Duration::from_millis( + config.seq_poll_timeout_millis, + )) + .await; + + latest_block_num = seq_client.get_last_block().await?.last_block; + } +} diff --git a/wallet/src/main.rs b/wallet/src/main.rs index b296400..ecc50d2 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Result; -use clap::Parser; +use clap::{CommandFactory, Parser}; use tokio::runtime::Builder; -use wallet::{Args, execute_subcommand}; +use wallet::{Args, execute_continious_run, execute_subcommand}; pub const NUM_THREADS: usize = 2; @@ -17,7 +17,14 @@ fn main() -> Result<()> { env_logger::init(); runtime.block_on(async move { - execute_subcommand(args.command).await.unwrap(); + if let Some(command) = args.command { + execute_subcommand(command).await.unwrap(); + } else if args.continious_run { + execute_continious_run().await.unwrap(); + } else { + let help = Args::command().render_long_help(); + println!("{help}"); + } }); Ok(()) diff --git a/wallet/src/token_transfers/private.rs b/wallet/src/token_transfers/private.rs index 0673d17..76265d8 100644 --- a/wallet/src/token_transfers/private.rs +++ b/wallet/src/token_transfers/private.rs @@ -6,7 +6,7 @@ use nssa::{ program::Program, }; use nssa_core::{ - Commitment, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, + Commitment, MembershipProof, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, encryption::IncomingViewingPublicKey, }; @@ -98,11 +98,12 @@ impl WalletCore { } } - pub async fn send_private_native_token_transfer_owned_account( + pub async fn send_private_native_token_transfer_owned_account_already_initialized( &self, from: Address, to: Address, balance_to_move: u128, + to_proof: MembershipProof, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { let Some((from_keys, from_acc)) = self.storage.user_data.get_private_account(&from).cloned() @@ -124,7 +125,6 @@ impl WalletCore { let program = Program::authenticated_transfer_program(); let sender_commitment = Commitment::new(&from_npk, &from_acc); - let receiver_commitment = Commitment::new(&to_npk, &to_acc); let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, &from_npk); let recipient_pre = AccountWithMetadata::new(to_acc.clone(), true, &to_npk); @@ -153,14 +153,7 @@ impl WalletCore { .unwrap() .unwrap(), ), - ( - to_keys.private_key_holder.nullifier_secret_key, - self.sequencer_client - .get_proof_for_commitment(receiver_commitment) - .await - .unwrap() - .unwrap(), - ), + (to_keys.private_key_holder.nullifier_secret_key, to_proof), ], &program, ) @@ -196,4 +189,92 @@ impl WalletCore { Err(ExecutionFailureKind::InsufficientFundsError) } } + + pub async fn send_private_native_token_transfer_owned_account_not_initialized( + &self, + from: Address, + to: Address, + balance_to_move: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let Some((from_keys, from_acc)) = + self.storage.user_data.get_private_account(&from).cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((to_keys, to_acc)) = self.storage.user_data.get_private_account(&to).cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let from_npk = from_keys.nullifer_public_key; + let from_ipk = from_keys.incoming_viewing_public_key; + let to_npk = to_keys.nullifer_public_key.clone(); + let to_ipk = to_keys.incoming_viewing_public_key.clone(); + + if from_acc.balance >= balance_to_move { + let program = Program::authenticated_transfer_program(); + + let sender_commitment = Commitment::new(&from_npk, &from_acc); + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, &from_npk); + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); + + 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], + &Program::serialize_instruction(balance_to_move).unwrap(), + &[1, 2], + &produce_random_nonces(2), + &[ + (from_npk.clone(), shared_secret_from.clone()), + (to_npk.clone(), shared_secret_to.clone()), + ], + &[( + from_keys.private_key_holder.nullifier_secret_key, + self.sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap() + .unwrap(), + )], + &program, + ) + .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], + )) + } else { + Err(ExecutionFailureKind::InsufficientFundsError) + } + } } diff --git a/wallet/src/token_transfers/shielded.rs b/wallet/src/token_transfers/shielded.rs index 6435bfe..c8cb24b 100644 --- a/wallet/src/token_transfers/shielded.rs +++ b/wallet/src/token_transfers/shielded.rs @@ -6,14 +6,84 @@ use nssa::{ program::Program, }; use nssa_core::{ - Commitment, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, + MembershipProof, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, encryption::IncomingViewingPublicKey, }; use crate::{WalletCore, helperfunctions::produce_random_nonces}; impl WalletCore { - pub async fn send_shielded_native_token_transfer( + pub async fn send_shielded_native_token_transfer_already_initialized( + &self, + from: Address, + to: Address, + balance_to_move: u128, + to_proof: MembershipProof, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((to_keys, to_acc)) = self.storage.user_data.get_private_account(&to).cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let to_npk = to_keys.nullifer_public_key.clone(); + let to_ipk = to_keys.incoming_viewing_public_key.clone(); + + if from_acc.balance >= balance_to_move { + let program = Program::authenticated_transfer_program(); + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), true, &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], + &nssa::program::Program::serialize_instruction(balance_to_move).unwrap(), + &[0, 1], + &produce_random_nonces(1), + &[(to_npk.clone(), shared_secret.clone())], + &[(to_keys.private_key_holder.nullifier_secret_key, to_proof)], + &program, + ) + .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, + )) + } else { + Err(ExecutionFailureKind::InsufficientFundsError) + } + } + + pub async fn send_shielded_native_token_transfer_not_initialized( &self, from: Address, to: Address, @@ -34,10 +104,8 @@ impl WalletCore { if from_acc.balance >= balance_to_move { let program = Program::authenticated_transfer_program(); - let receiver_commitment = Commitment::new(&to_npk, &to_acc); - let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); - let recipient_pre = AccountWithMetadata::new(to_acc.clone(), true, &to_npk); + 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); @@ -45,17 +113,10 @@ impl WalletCore { let (output, proof) = circuit::execute_and_prove( &[sender_pre, recipient_pre], &nssa::program::Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 1], + &[0, 2], &produce_random_nonces(1), &[(to_npk.clone(), shared_secret.clone())], - &[( - to_keys.private_key_holder.nullifier_secret_key, - self.sequencer_client - .get_proof_for_commitment(receiver_commitment) - .await - .unwrap() - .unwrap(), - )], + &[], &program, ) .unwrap();