diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 7790ea0..fd38138 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,9 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use generic_array::GenericArray; use k256::ecdsa::{Signature, SigningKey, VerifyingKey}; use log::info; use serde::{Deserialize, Serialize}; -use generic_array::GenericArray; use sha2::digest::typenum::{B0, B1}; use sha2::digest::typenum::{UInt, UTerm}; use sha2::{Digest, digest::FixedOutput}; diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 78b7a6b..9b13f01 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -19,6 +19,10 @@ use tempfile::TempDir; use tokio::task::JoinHandle; use wallet::{ Command, SubcommandReturnValue, WalletCore, + cli::token_program::{ + TokenProgramSubcommand, TokenProgramSubcommandDeshielded, TokenProgramSubcommandPrivate, + TokenProgramSubcommandPublic, TokenProgramSubcommandShielded, + }, config::PersistentAccountData, helperfunctions::{fetch_config, fetch_persistent_accounts}, }; @@ -350,13 +354,15 @@ pub async fn test_success_token_program() { .expect("Failed to produce new account, not present in persistent accounts"); // Create new token - let command = Command::CreateNewToken { + let subcommand = TokenProgramSubcommand::Public(TokenProgramSubcommandPublic::CreateNewToken { definition_addr: definition_addr.to_string(), supply_addr: supply_addr.to_string(), name: "A NAME".to_string(), total_supply: 37, - }; - wallet::execute_subcommand(command).await.unwrap(); + }); + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .await + .unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -402,12 +408,14 @@ pub async fn test_success_token_program() { ); // Transfer 7 tokens from `supply_acc` to the account at address `recipient_addr` - let command = Command::TransferToken { + let subcommand = TokenProgramSubcommand::Public(TokenProgramSubcommandPublic::TransferToken { sender_addr: supply_addr.to_string(), recipient_addr: recipient_addr.to_string(), balance_to_move: 7, - }; - wallet::execute_subcommand(command).await.unwrap(); + }); + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .await + .unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -447,6 +455,504 @@ 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(); + + // Create new account for the token definition (public) + let SubcommandReturnValue::RegisterAccount { + addr: definition_addr, + } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) + .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 { + 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() + else { + panic!("invalid subcommand return value"); + }; + + // Create new token + let subcommand = TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned { + definition_addr: definition_addr.to_string(), + supply_addr: supply_addr.to_string(), + name: "A NAME".to_string(), + total_supply: 37, + }, + ); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .await + .unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + // Check the status of the token definition account is the expected after the execution + let definition_acc = seq_client + .get_account(definition_addr.to_string()) + .await + .unwrap() + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + assert_eq!( + definition_acc.data, + vec![ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let wallet_config = fetch_config().unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&supply_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + // Transfer 7 tokens from `supply_acc` to the account at address `recipient_addr` + let subcommand = + TokenProgramSubcommand::Private(TokenProgramSubcommandPrivate::TransferTokenPrivateOwned { + sender_addr: supply_addr.to_string(), + recipient_addr: recipient_addr.to_string(), + balance_to_move: 7, + }); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .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_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&supply_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + let new_commitment2 = wallet_storage + .get_private_account_commitment(&recipient_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); + + // Transfer additional 7 tokens from `supply_acc` to the account at address `recipient_addr` + let subcommand = + TokenProgramSubcommand::Private(TokenProgramSubcommandPrivate::TransferTokenPrivateOwned { + sender_addr: supply_addr.to_string(), + recipient_addr: recipient_addr.to_string(), + balance_to_move: 7, + }); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .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_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&supply_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + let new_commitment2 = wallet_storage + .get_private_account_commitment(&recipient_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); +} + +/// This test creates a new private token using the token program. After creating the token, the test executes a +/// private token transfer to a new account. +pub async fn test_success_token_program_private_claiming_path() { + let wallet_config = fetch_config().unwrap(); + + // Create new account for the token definition (public) + let SubcommandReturnValue::RegisterAccount { + addr: definition_addr, + } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) + .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 { + 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() + else { + panic!("invalid subcommand return value"); + }; + + // Create new token + let subcommand = TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned { + definition_addr: definition_addr.to_string(), + supply_addr: supply_addr.to_string(), + name: "A NAME".to_string(), + total_supply: 37, + }, + ); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .await + .unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + // Check the status of the token definition account is the expected after the execution + let definition_acc = seq_client + .get_account(definition_addr.to_string()) + .await + .unwrap() + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + assert_eq!( + definition_acc.data, + vec![ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let wallet_config = fetch_config().unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&supply_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + let (recipient_keys, _) = wallet_storage + .storage + .user_data + .get_private_account(&recipient_addr) + .unwrap(); + + // Transfer 7 tokens from `supply_acc` to the account at address `recipient_addr` + let subcommand = TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::TransferTokenPrivateForeign { + sender_addr: supply_addr.to_string(), + recipient_npk: hex::encode(recipient_keys.nullifer_public_key.0), + recipient_ipk: hex::encode(recipient_keys.incoming_viewing_public_key.0.clone()), + balance_to_move: 7, + }, + ); + + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let command = Command::FetchPrivateAccount { + 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 new_commitment1 = wallet_storage + .get_private_account_commitment(&supply_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + let new_commitment2 = wallet_storage + .get_private_account_commitment(&recipient_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); +} + +/// 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(); + + // Create new account for the token definition (public) + let SubcommandReturnValue::RegisterAccount { + addr: definition_addr, + } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) + .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 { + 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() + else { + panic!("invalid subcommand return value"); + }; + + // Create new token + let subcommand = TokenProgramSubcommand::Public(TokenProgramSubcommandPublic::CreateNewToken { + definition_addr: definition_addr.to_string(), + supply_addr: supply_addr.to_string(), + name: "A NAME".to_string(), + total_supply: 37, + }); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .await + .unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + // Check the status of the token definition account is the expected after the execution + let definition_acc = seq_client + .get_account(definition_addr.to_string()) + .await + .unwrap() + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + assert_eq!( + definition_acc.data, + vec![ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Transfer 7 tokens from `supply_acc` to the account at address `recipient_addr` + let subcommand = TokenProgramSubcommand::Shielded( + TokenProgramSubcommandShielded::TransferTokenShieldedOwned { + sender_addr: supply_addr.to_string(), + recipient_addr: recipient_addr.to_string(), + balance_to_move: 7, + }, + ); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .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_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment2 = wallet_storage + .get_private_account_commitment(&recipient_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); + + // Transfer additional 7 tokens from `supply_acc` to the account at address `recipient_addr` + let subcommand = TokenProgramSubcommand::Shielded( + TokenProgramSubcommandShielded::TransferTokenShieldedOwned { + sender_addr: supply_addr.to_string(), + recipient_addr: recipient_addr.to_string(), + balance_to_move: 7, + }, + ); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .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_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment2 = wallet_storage + .get_private_account_commitment(&recipient_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); +} + +/// This test creates a new private token using the token program. After creating the token, the test executes a +/// 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(); + + // Create new account for the token definition (public) + let SubcommandReturnValue::RegisterAccount { + addr: definition_addr, + } = wallet::execute_subcommand(Command::RegisterAccountPublic {}) + .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 { + 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() + else { + panic!("invalid subcommand return value"); + }; + + // Create new token + let subcommand = TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned { + definition_addr: definition_addr.to_string(), + supply_addr: supply_addr.to_string(), + name: "A NAME".to_string(), + total_supply: 37, + }, + ); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .await + .unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + // Check the status of the token definition account is the expected after the execution + let definition_acc = seq_client + .get_account(definition_addr.to_string()) + .await + .unwrap() + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] + assert_eq!( + definition_acc.data, + vec![ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let wallet_config = fetch_config().unwrap(); + let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&supply_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + // Transfer 7 tokens from `supply_acc` to the account at address `recipient_addr` + let subcommand = TokenProgramSubcommand::Deshielded( + TokenProgramSubcommandDeshielded::TransferTokenDeshielded { + sender_addr: supply_addr.to_string(), + recipient_addr: recipient_addr.to_string(), + balance_to_move: 7, + }, + ); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .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_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&supply_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); + + // Transfer additional 7 tokens from `supply_acc` to the account at address `recipient_addr` + let subcommand = TokenProgramSubcommand::Deshielded( + TokenProgramSubcommandDeshielded::TransferTokenDeshielded { + sender_addr: supply_addr.to_string(), + recipient_addr: recipient_addr.to_string(), + balance_to_move: 7, + }, + ); + + wallet::execute_subcommand(Command::TokenProgram(subcommand)) + .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_storage = WalletCore::start_from_config_update_chain(wallet_config).unwrap(); + + let new_commitment1 = wallet_storage + .get_private_account_commitment(&supply_addr) + .unwrap(); + assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); +} + pub async fn test_success_private_transfer_to_another_owned_account() { info!("test_success_private_transfer_to_another_owned_account"); let from: Address = ACC_SENDER_PRIVATE.parse().unwrap(); @@ -946,9 +1452,21 @@ pub async fn main_tests_runner() -> Result<()> { "test_pinata_private_receiver" => { test_cleanup_wrap!(home_dir, test_pinata_private_receiver); } + "test_success_token_program_private_owned" => { + test_cleanup_wrap!(home_dir, test_success_token_program_private_owned); + } + "test_success_token_program_private_claiming_path" => { + test_cleanup_wrap!(home_dir, test_success_token_program_private_claiming_path); + } "test_pinata_private_receiver_new_account" => { test_cleanup_wrap!(home_dir, test_pinata_private_receiver_new_account); } + "test_success_token_program_shielded_owned" => { + test_cleanup_wrap!(home_dir, test_success_token_program_shielded_owned); + } + "test_success_token_program_deshielded_owned" => { + test_cleanup_wrap!(home_dir, test_success_token_program_deshielded_owned); + } "all" => { test_cleanup_wrap!(home_dir, test_success_move_to_another_account); test_cleanup_wrap!(home_dir, test_success); @@ -981,6 +1499,8 @@ pub async fn main_tests_runner() -> Result<()> { ); test_cleanup_wrap!(home_dir, test_pinata); 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_private_claiming_path); test_cleanup_wrap!(home_dir, test_pinata_private_receiver_new_account); } "all_private" => { @@ -1009,6 +1529,8 @@ pub async fn main_tests_runner() -> Result<()> { test_success_private_transfer_to_another_owned_account_claiming_path ); 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_private_claiming_path); test_cleanup_wrap!(home_dir, test_pinata_private_receiver_new_account); } _ => { diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs new file mode 100644 index 0000000..5b277e0 --- /dev/null +++ b/wallet/src/cli/mod.rs @@ -0,0 +1,10 @@ +use anyhow::Result; + +use crate::{SubcommandReturnValue, WalletCore}; + +pub mod token_program; + +pub(crate) trait WalletSubcommand { + async fn handle_subcommand(self, wallet_core: &mut WalletCore) + -> Result; +} diff --git a/wallet/src/cli/token_program.rs b/wallet/src/cli/token_program.rs new file mode 100644 index 0000000..02b25b1 --- /dev/null +++ b/wallet/src/cli/token_program.rs @@ -0,0 +1,518 @@ +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 token_program +#[derive(Subcommand, Debug, Clone)] +pub enum TokenProgramSubcommand { + ///Public execution + #[command(subcommand)] + Public(TokenProgramSubcommandPublic), + ///Private execution + #[command(subcommand)] + Private(TokenProgramSubcommandPrivate), + ///Deshielded execution + #[command(subcommand)] + Deshielded(TokenProgramSubcommandDeshielded), + ///Shielded execution + #[command(subcommand)] + Shielded(TokenProgramSubcommandShielded), +} + +///Represents generic public CLI subcommand for a wallet working with token_program +#[derive(Subcommand, Debug, Clone)] +pub enum TokenProgramSubcommandPublic { + //Create a new token using the token program + CreateNewToken { + #[arg(short, long)] + definition_addr: String, + #[arg(short, long)] + supply_addr: String, + #[arg(short, long)] + name: String, + #[arg(short, long)] + total_supply: u128, + }, + //Transfer tokens using the token program + TransferToken { + #[arg(short, long)] + sender_addr: String, + #[arg(short, long)] + recipient_addr: String, + #[arg(short, long)] + balance_to_move: u128, + }, +} + +///Represents generic private CLI subcommand for a wallet working with token_program +#[derive(Subcommand, Debug, Clone)] +pub enum TokenProgramSubcommandPrivate { + //Create a new token using the token program + CreateNewTokenPrivateOwned { + #[arg(short, long)] + definition_addr: String, + #[arg(short, long)] + supply_addr: String, + #[arg(short, long)] + name: String, + #[arg(short, long)] + total_supply: u128, + }, + //Transfer tokens using the token program + TransferTokenPrivateOwned { + #[arg(short, long)] + sender_addr: String, + #[arg(short, long)] + recipient_addr: String, + #[arg(short, long)] + balance_to_move: u128, + }, + //Transfer tokens using the token program + TransferTokenPrivateForeign { + #[arg(short, long)] + sender_addr: String, + ///recipient_npk - valid 32 byte hex string + #[arg(long)] + recipient_npk: String, + ///recipient_ipk - valid 33 byte hex string + #[arg(long)] + recipient_ipk: String, + #[arg(short, long)] + balance_to_move: u128, + }, +} + +///Represents deshielded public CLI subcommand for a wallet working with token_program +#[derive(Subcommand, Debug, Clone)] +pub enum TokenProgramSubcommandDeshielded { + //Transfer tokens using the token program + TransferTokenDeshielded { + #[arg(short, long)] + sender_addr: String, + #[arg(short, long)] + recipient_addr: String, + #[arg(short, long)] + balance_to_move: u128, + }, +} + +///Represents generic shielded CLI subcommand for a wallet working with token_program +#[derive(Subcommand, Debug, Clone)] +pub enum TokenProgramSubcommandShielded { + //Transfer tokens using the token program + TransferTokenShieldedOwned { + #[arg(short, long)] + sender_addr: String, + #[arg(short, long)] + recipient_addr: String, + #[arg(short, long)] + balance_to_move: u128, + }, + //Transfer tokens using the token program + TransferTokenShieldedForeign { + #[arg(short, long)] + sender_addr: String, + ///recipient_npk - valid 32 byte hex string + #[arg(long)] + recipient_npk: String, + ///recipient_ipk - valid 33 byte hex string + #[arg(long)] + recipient_ipk: String, + #[arg(short, long)] + balance_to_move: u128, + }, +} + +impl WalletSubcommand for TokenProgramSubcommandPublic { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + TokenProgramSubcommandPublic::CreateNewToken { + definition_addr, + supply_addr, + name, + total_supply, + } => { + let name = name.as_bytes(); + if name.len() > 6 { + // TODO: return error + panic!(); + } + let mut name_bytes = [0; 6]; + name_bytes[..name.len()].copy_from_slice(name); + wallet_core + .send_new_token_definition( + definition_addr.parse().unwrap(), + supply_addr.parse().unwrap(), + name_bytes, + total_supply, + ) + .await?; + Ok(SubcommandReturnValue::Empty) + } + TokenProgramSubcommandPublic::TransferToken { + sender_addr, + recipient_addr, + balance_to_move, + } => { + wallet_core + .send_transfer_token_transaction( + sender_addr.parse().unwrap(), + recipient_addr.parse().unwrap(), + balance_to_move, + ) + .await?; + Ok(SubcommandReturnValue::Empty) + } + } + } +} + +impl WalletSubcommand for TokenProgramSubcommandPrivate { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + TokenProgramSubcommandPrivate::CreateNewTokenPrivateOwned { + definition_addr, + supply_addr, + name, + total_supply, + } => { + let name = name.as_bytes(); + if name.len() > 6 { + // TODO: return error + panic!("Name length mismatch"); + } + let mut name_bytes = [0; 6]; + name_bytes[..name.len()].copy_from_slice(name); + + let definition_addr: Address = definition_addr.parse().unwrap(); + let supply_addr: Address = supply_addr.parse().unwrap(); + + let (res, [secret_supply]) = wallet_core + .send_new_token_definition_private_owned( + definition_addr, + supply_addr, + name_bytes, + total_supply, + ) + .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_supply, supply_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:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandPrivate::TransferTokenPrivateOwned { + sender_addr, + recipient_addr, + balance_to_move, + } => { + let sender_addr: Address = sender_addr.parse().unwrap(); + let recipient_addr: Address = recipient_addr.parse().unwrap(); + + let recipient_initialization = wallet_core + .check_private_account_initialized(&recipient_addr) + .await?; + + let (res, [secret_sender, secret_recipient]) = + if let Some(recipient_proof) = recipient_initialization { + wallet_core + .send_transfer_token_transaction_private_owned_account_already_initialized( + sender_addr, + recipient_addr, + balance_to_move, + recipient_proof, + ) + .await? + } else { + wallet_core + .send_transfer_token_transaction_private_owned_account_not_initialized( + sender_addr, + recipient_addr, + balance_to_move, + ) + .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_sender, sender_addr), + (secret_recipient, recipient_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:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandPrivate::TransferTokenPrivateForeign { + sender_addr, + recipient_npk, + recipient_ipk, + balance_to_move, + } => { + let sender_addr: Address = sender_addr.parse().unwrap(); + let recipient_npk_res = hex::decode(recipient_npk)?; + let mut recipient_npk = [0; 32]; + recipient_npk.copy_from_slice(&recipient_npk_res); + let recipient_npk = nssa_core::NullifierPublicKey(recipient_npk); + + let recipient_ipk_res = hex::decode(recipient_ipk)?; + let mut recipient_ipk = [0u8; 33]; + recipient_ipk.copy_from_slice(&recipient_ipk_res); + let recipient_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( + recipient_ipk.to_vec(), + ); + + let (res, [secret_sender, _]) = wallet_core + .send_transfer_token_transaction_private_foreign_account( + sender_addr, + recipient_npk, + recipient_ipk, + balance_to_move, + ) + .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_sender, sender_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:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + } + } +} + +impl WalletSubcommand for TokenProgramSubcommandDeshielded { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + TokenProgramSubcommandDeshielded::TransferTokenDeshielded { + sender_addr, + recipient_addr, + balance_to_move, + } => { + let sender_addr: Address = sender_addr.parse().unwrap(); + let recipient_addr: Address = recipient_addr.parse().unwrap(); + + let (res, [secret_sender]) = wallet_core + .send_transfer_token_transaction_deshielded( + sender_addr, + recipient_addr, + balance_to_move, + ) + .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_sender, sender_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:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + } + } +} + +impl WalletSubcommand for TokenProgramSubcommandShielded { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + TokenProgramSubcommandShielded::TransferTokenShieldedForeign { + sender_addr, + recipient_npk, + recipient_ipk, + balance_to_move, + } => { + let sender_addr: Address = sender_addr.parse().unwrap(); + let recipient_npk_res = hex::decode(recipient_npk)?; + let mut recipient_npk = [0; 32]; + recipient_npk.copy_from_slice(&recipient_npk_res); + let recipient_npk = nssa_core::NullifierPublicKey(recipient_npk); + + let recipient_ipk_res = hex::decode(recipient_ipk)?; + let mut recipient_ipk = [0u8; 33]; + recipient_ipk.copy_from_slice(&recipient_ipk_res); + let recipient_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( + recipient_ipk.to_vec(), + ); + + let res = wallet_core + .send_transfer_token_transaction_shielded_foreign_account( + sender_addr, + recipient_npk, + recipient_ipk, + balance_to_move, + ) + .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 { + println!("Transaction data is {:?}", tx.message); + } + + let path = wallet_core.store_persistent_accounts()?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandShielded::TransferTokenShieldedOwned { + sender_addr, + recipient_addr, + balance_to_move, + } => { + let sender_addr: Address = sender_addr.parse().unwrap(); + let recipient_addr: Address = recipient_addr.parse().unwrap(); + + let recipient_initialization = wallet_core + .check_private_account_initialized(&recipient_addr) + .await?; + + let (res, [secret_recipient]) = + if let Some(recipient_proof) = recipient_initialization { + wallet_core + .send_transfer_token_transaction_shielded_owned_account_already_initialized( + sender_addr, + recipient_addr, + balance_to_move, + recipient_proof, + ) + .await? + } else { + wallet_core + .send_transfer_token_transaction_shielded_owned_account_not_initialized( + sender_addr, + recipient_addr, + balance_to_move, + ) + .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_recipient, recipient_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:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + } + } +} + +impl WalletSubcommand for TokenProgramSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + TokenProgramSubcommand::Private(private_subcommand) => { + private_subcommand.handle_subcommand(wallet_core).await + } + TokenProgramSubcommand::Public(public_subcommand) => { + public_subcommand.handle_subcommand(wallet_core).await + } + TokenProgramSubcommand::Deshielded(deshielded_subcommand) => { + deshielded_subcommand.handle_subcommand(wallet_core).await + } + TokenProgramSubcommand::Shielded(shielded_subcommand) => { + shielded_subcommand.handle_subcommand(wallet_core).await + } + } + } +} diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index eb68a5c..31cf483 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -2,8 +2,7 @@ use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use common::{ - ExecutionFailureKind, - sequencer_client::{SequencerClient, json::SendTxResponse}, + sequencer_client::SequencerClient, transaction::{EncodedTransaction, NSSATransaction}, }; @@ -16,7 +15,9 @@ use nssa::{Account, Address, program::Program}; use clap::{Parser, Subcommand}; use nssa_core::{Commitment, MembershipProof}; +use crate::cli::WalletSubcommand; use crate::{ + cli::token_program::TokenProgramSubcommand, helperfunctions::{ HumanReadableAccount, fetch_config, fetch_persistent_accounts, get_home, produce_data_for_storage, @@ -27,10 +28,12 @@ use crate::{ pub const HOME_DIR_ENV_VAR: &str = "NSSA_WALLET_HOME_DIR"; pub mod chain_storage; +pub mod cli; pub mod config; pub mod helperfunctions; pub mod pinata_interactions; pub mod poller; +pub mod token_program_interactions; pub mod token_transfers; pub struct WalletCore { @@ -86,63 +89,6 @@ impl WalletCore { .generate_new_privacy_preserving_transaction_key_chain() } - pub async fn send_new_token_definition( - &self, - definition_address: Address, - supply_address: Address, - name: [u8; 6], - total_supply: u128, - ) -> Result { - let addresses = vec![definition_address, supply_address]; - let program_id = nssa::program::Program::token().id(); - // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction = [0; 23]; - instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); - instruction[17..].copy_from_slice(&name); - let message = - nssa::public_transaction::Message::try_new(program_id, addresses, vec![], instruction) - .unwrap(); - - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); - - let tx = nssa::PublicTransaction::new(message, witness_set); - - Ok(self.sequencer_client.send_tx_public(tx).await?) - } - - pub async fn send_transfer_token_transaction( - &self, - sender_address: Address, - recipient_address: Address, - amount: u128, - ) -> Result { - let addresses = vec![sender_address, recipient_address]; - let program_id = nssa::program::Program::token().id(); - // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. - let mut instruction = [0; 23]; - instruction[0] = 0x01; - instruction[1..17].copy_from_slice(&amount.to_le_bytes()); - let Ok(nonces) = self.get_accounts_nonces(vec![sender_address]).await else { - return Err(ExecutionFailureKind::SequencerError); - }; - let message = - nssa::public_transaction::Message::try_new(program_id, addresses, nonces, instruction) - .unwrap(); - - let Some(signing_key) = self - .storage - .user_data - .get_pub_account_signing_key(&sender_address) - else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - let witness_set = - nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); - - let tx = nssa::PublicTransaction::new(message, witness_set); - - Ok(self.sequencer_client.send_tx_public(tx).await?) - } ///Get account balance pub async fn get_account_balance(&self, acc: Address) -> Result { Ok(self @@ -367,26 +313,6 @@ pub enum Command { #[arg(short, long)] addr: String, }, - //Create a new token using the token program - CreateNewToken { - #[arg(short, long)] - definition_addr: String, - #[arg(short, long)] - supply_addr: String, - #[arg(short, long)] - name: String, - #[arg(short, long)] - total_supply: u128, - }, - //Transfer tokens using the token program - TransferToken { - #[arg(short, long)] - sender_addr: String, - #[arg(short, long)] - recipient_addr: String, - #[arg(short, long)] - balance_to_move: u128, - }, // TODO: Testnet only. Refactor to prevent compilation on mainnet. // Claim piƱata prize ClaimPinata { @@ -416,6 +342,9 @@ pub enum Command { #[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 @@ -742,43 +671,6 @@ pub async fn execute_subcommand(command: Command) -> Result { - let name = name.as_bytes(); - if name.len() > 6 { - // TODO: return error - panic!(); - } - let mut name_bytes = [0; 6]; - name_bytes[..name.len()].copy_from_slice(name); - wallet_core - .send_new_token_definition( - definition_addr.parse().unwrap(), - supply_addr.parse().unwrap(), - name_bytes, - total_supply, - ) - .await?; - SubcommandReturnValue::Empty - } - Command::TransferToken { - sender_addr, - recipient_addr, - balance_to_move, - } => { - wallet_core - .send_transfer_token_transaction( - sender_addr.parse().unwrap(), - recipient_addr.parse().unwrap(), - balance_to_move, - ) - .await?; - SubcommandReturnValue::Empty - } Command::ClaimPinata { pinata_addr, winner_addr, @@ -876,6 +768,9 @@ pub async fn execute_subcommand(command: Command) -> Result { + token_subcommand.handle_subcommand(&mut wallet_core).await? + } }; Ok(subcommand_ret) diff --git a/wallet/src/token_program_interactions.rs b/wallet/src/token_program_interactions.rs new file mode 100644 index 0000000..a77a512 --- /dev/null +++ b/wallet/src/token_program_interactions.rs @@ -0,0 +1,772 @@ +use common::{ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use nssa::{Address, privacy_preserving_transaction::circuit, program::Program}; +use nssa_core::{ + Commitment, MembershipProof, NullifierPublicKey, SharedSecretKey, account::AccountWithMetadata, + encryption::IncomingViewingPublicKey, +}; + +use crate::{WalletCore, helperfunctions::produce_random_nonces}; + +impl WalletCore { + pub async fn send_new_token_definition( + &self, + definition_address: Address, + supply_address: Address, + name: [u8; 6], + total_supply: u128, + ) -> Result { + let addresses = vec![definition_address, supply_address]; + let program_id = nssa::program::Program::token().id(); + // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] + let mut instruction = [0; 23]; + instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); + instruction[17..].copy_from_slice(&name); + let message = + nssa::public_transaction::Message::try_new(program_id, addresses, vec![], instruction) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_new_token_definition_private_owned( + &self, + definition_addr: Address, + supply_addr: Address, + name: [u8; 6], + total_supply: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Some((supply_keys, supply_acc)) = self + .storage + .user_data + .get_private_account(&supply_addr) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + //It makes more sence to have definition acc as public + let definition_acc = self.get_account_public(definition_addr).await.unwrap(); + + let supply_npk = supply_keys.nullifer_public_key; + let supply_ipk = supply_keys.incoming_viewing_public_key; + + let program = nssa::program::Program::token(); + + let definition_pre = + AccountWithMetadata::new(definition_acc.clone(), false, definition_addr); + let supply_pre = AccountWithMetadata::new(supply_acc.clone(), false, &supply_npk); + + let eph_holder_supply = EphemeralKeyHolder::new(&supply_npk); + let shared_secret_supply = eph_holder_supply.calculate_shared_secret_sender(&supply_ipk); + + // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] + let mut instruction = [0; 23]; + instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); + instruction[17..].copy_from_slice(&name); + + let (output, proof) = circuit::execute_and_prove( + &[definition_pre, supply_pre], + &nssa::program::Program::serialize_instruction(instruction).unwrap(), + &[0, 2], + &produce_random_nonces(1), + &[(supply_npk.clone(), shared_secret_supply.clone())], + &[], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![definition_addr], + vec![], + vec![( + supply_npk.clone(), + supply_ipk.clone(), + eph_holder_supply.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_supply], + )) + } + + pub async fn send_transfer_token_transaction( + &self, + sender_address: Address, + recipient_address: Address, + amount: u128, + ) -> Result { + let addresses = vec![sender_address, recipient_address]; + let program_id = nssa::program::Program::token().id(); + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + let Ok(nonces) = self.get_accounts_nonces(vec![sender_address]).await else { + return Err(ExecutionFailureKind::SequencerError); + }; + let message = + nssa::public_transaction::Message::try_new(program_id, addresses, nonces, instruction) + .unwrap(); + + let Some(signing_key) = self + .storage + .user_data + .get_pub_account_signing_key(&sender_address) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_transfer_token_transaction_private_owned_account_already_initialized( + &self, + sender_address: Address, + recipient_address: Address, + amount: u128, + recipient_proof: MembershipProof, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let Some((sender_keys, sender_acc)) = self + .storage + .user_data + .get_private_account(&sender_address) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((recipient_keys, recipient_acc)) = self + .storage + .user_data + .get_private_account(&recipient_address) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let sender_npk = sender_keys.nullifer_public_key; + let sender_ipk = sender_keys.incoming_viewing_public_key; + let recipient_npk = recipient_keys.nullifer_public_key.clone(); + let recipient_ipk = recipient_keys.incoming_viewing_public_key.clone(); + + let program = Program::token(); + + let sender_commitment = Commitment::new(&sender_npk, &sender_acc); + + let sender_pre = AccountWithMetadata::new(sender_acc.clone(), true, &sender_npk); + let recipient_pre = AccountWithMetadata::new(recipient_acc.clone(), true, &recipient_npk); + + let eph_holder_sender = EphemeralKeyHolder::new(&sender_npk); + let shared_secret_sender = eph_holder_sender.calculate_shared_secret_sender(&sender_ipk); + + let eph_holder_recipient = EphemeralKeyHolder::new(&recipient_npk); + let shared_secret_recipient = + eph_holder_recipient.calculate_shared_secret_sender(&recipient_ipk); + + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[1, 1], + &produce_random_nonces(2), + &[ + (sender_npk.clone(), shared_secret_sender.clone()), + (recipient_npk.clone(), shared_secret_recipient.clone()), + ], + &[ + ( + sender_keys.private_key_holder.nullifier_secret_key, + self.sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap() + .unwrap(), + ), + ( + recipient_keys.private_key_holder.nullifier_secret_key, + recipient_proof, + ), + ], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + sender_npk.clone(), + sender_ipk.clone(), + eph_holder_sender.generate_ephemeral_public_key(), + ), + ( + recipient_npk.clone(), + recipient_ipk.clone(), + eph_holder_recipient.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_sender, shared_secret_recipient], + )) + } + + pub async fn send_transfer_token_transaction_private_owned_account_not_initialized( + &self, + sender_address: Address, + recipient_address: Address, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let Some((sender_keys, sender_acc)) = self + .storage + .user_data + .get_private_account(&sender_address) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((recipient_keys, recipient_acc)) = self + .storage + .user_data + .get_private_account(&recipient_address) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let sender_npk = sender_keys.nullifer_public_key; + let sender_ipk = sender_keys.incoming_viewing_public_key; + let recipient_npk = recipient_keys.nullifer_public_key.clone(); + let recipient_ipk = recipient_keys.incoming_viewing_public_key.clone(); + + let program = Program::token(); + + let sender_commitment = Commitment::new(&sender_npk, &sender_acc); + + let sender_pre = AccountWithMetadata::new(sender_acc.clone(), true, &sender_npk); + let recipient_pre = AccountWithMetadata::new(recipient_acc.clone(), false, &recipient_npk); + + let eph_holder_sender = EphemeralKeyHolder::new(&sender_npk); + let shared_secret_sender = eph_holder_sender.calculate_shared_secret_sender(&sender_ipk); + + let eph_holder_recipient = EphemeralKeyHolder::new(&recipient_npk); + let shared_secret_recipient = + eph_holder_recipient.calculate_shared_secret_sender(&recipient_ipk); + + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[1, 2], + &produce_random_nonces(2), + &[ + (sender_npk.clone(), shared_secret_sender.clone()), + (recipient_npk.clone(), shared_secret_recipient.clone()), + ], + &[( + sender_keys.private_key_holder.nullifier_secret_key, + self.sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap() + .unwrap(), + )], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + sender_npk.clone(), + sender_ipk.clone(), + eph_holder_sender.generate_ephemeral_public_key(), + ), + ( + recipient_npk.clone(), + recipient_ipk.clone(), + eph_holder_recipient.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_sender, shared_secret_recipient], + )) + } + + pub async fn send_transfer_token_transaction_private_foreign_account( + &self, + sender_address: Address, + recipient_npk: NullifierPublicKey, + recipient_ipk: IncomingViewingPublicKey, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let Some((sender_keys, sender_acc)) = self + .storage + .user_data + .get_private_account(&sender_address) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let recipient_acc = nssa_core::account::Account::default(); + + let sender_npk = sender_keys.nullifer_public_key; + let sender_ipk = sender_keys.incoming_viewing_public_key; + + let program = Program::token(); + + let sender_commitment = Commitment::new(&sender_npk, &sender_acc); + + let sender_pre = AccountWithMetadata::new(sender_acc.clone(), true, &sender_npk); + let recipient_pre = AccountWithMetadata::new(recipient_acc.clone(), false, &recipient_npk); + + let eph_holder_sender = EphemeralKeyHolder::new(&sender_npk); + let shared_secret_sender = eph_holder_sender.calculate_shared_secret_sender(&sender_ipk); + + let eph_holder_recipient = EphemeralKeyHolder::new(&recipient_npk); + let shared_secret_recipient = + eph_holder_recipient.calculate_shared_secret_sender(&recipient_ipk); + + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[1, 2], + &produce_random_nonces(2), + &[ + (sender_npk.clone(), shared_secret_sender.clone()), + (recipient_npk.clone(), shared_secret_recipient.clone()), + ], + &[( + sender_keys.private_key_holder.nullifier_secret_key, + self.sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap() + .unwrap(), + )], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + sender_npk.clone(), + sender_ipk.clone(), + eph_holder_sender.generate_ephemeral_public_key(), + ), + ( + recipient_npk.clone(), + recipient_ipk.clone(), + eph_holder_recipient.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_sender, shared_secret_recipient], + )) + } + + pub async fn send_transfer_token_transaction_deshielded( + &self, + sender_address: Address, + recipient_address: Address, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Some((sender_keys, sender_acc)) = self + .storage + .user_data + .get_private_account(&sender_address) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Ok(recipient_acc) = self.get_account_public(recipient_address).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let sender_npk = sender_keys.nullifer_public_key; + let sender_ipk = sender_keys.incoming_viewing_public_key; + + let program = Program::token(); + + let sender_commitment = Commitment::new(&sender_npk, &sender_acc); + + let sender_pre = AccountWithMetadata::new(sender_acc.clone(), true, &sender_npk); + let recipient_pre = + AccountWithMetadata::new(recipient_acc.clone(), false, recipient_address); + + let eph_holder_sender = EphemeralKeyHolder::new(&sender_npk); + let shared_secret_sender = eph_holder_sender.calculate_shared_secret_sender(&sender_ipk); + + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[1, 0], + &produce_random_nonces(1), + &[(sender_npk.clone(), shared_secret_sender.clone())], + &[( + sender_keys.private_key_holder.nullifier_secret_key, + self.sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap() + .unwrap(), + )], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![recipient_address], + vec![], + vec![( + sender_npk.clone(), + sender_ipk.clone(), + eph_holder_sender.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_sender], + )) + } + + pub async fn send_transfer_token_transaction_shielded_owned_account_already_initialized( + &self, + sender_address: Address, + recipient_address: Address, + amount: u128, + recipient_proof: MembershipProof, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Ok(sender_acc) = self.get_account_public(sender_address).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some(sender_priv_key) = self + .storage + .user_data + .get_pub_account_signing_key(&sender_address) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((recipient_keys, recipient_acc)) = self + .storage + .user_data + .get_private_account(&recipient_address) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let recipient_npk = recipient_keys.nullifer_public_key.clone(); + let recipient_ipk = recipient_keys.incoming_viewing_public_key.clone(); + + let program = Program::token(); + + let sender_pre = AccountWithMetadata::new(sender_acc.clone(), true, sender_address); + let recipient_pre = AccountWithMetadata::new(recipient_acc.clone(), true, &recipient_npk); + + let eph_holder_recipient = EphemeralKeyHolder::new(&recipient_npk); + let shared_secret_recipient = + eph_holder_recipient.calculate_shared_secret_sender(&recipient_ipk); + + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[0, 1], + &produce_random_nonces(1), + &[(recipient_npk.clone(), shared_secret_recipient.clone())], + &[( + recipient_keys.private_key_holder.nullifier_secret_key, + recipient_proof, + )], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![sender_address], + vec![sender_acc.nonce], + vec![( + recipient_npk.clone(), + recipient_ipk.clone(), + eph_holder_recipient.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[sender_priv_key], + ); + let tx = nssa::PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_recipient], + )) + } + + pub async fn send_transfer_token_transaction_shielded_owned_account_not_initialized( + &self, + sender_address: Address, + recipient_address: Address, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Ok(sender_acc) = self.get_account_public(sender_address).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some(sender_priv_key) = self + .storage + .user_data + .get_pub_account_signing_key(&sender_address) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some((recipient_keys, recipient_acc)) = self + .storage + .user_data + .get_private_account(&recipient_address) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let recipient_npk = recipient_keys.nullifer_public_key.clone(); + let recipient_ipk = recipient_keys.incoming_viewing_public_key.clone(); + + let program = Program::token(); + + let sender_pre = AccountWithMetadata::new(sender_acc.clone(), true, sender_address); + let recipient_pre = AccountWithMetadata::new(recipient_acc.clone(), false, &recipient_npk); + + let eph_holder_recipient = EphemeralKeyHolder::new(&recipient_npk); + let shared_secret_recipient = + eph_holder_recipient.calculate_shared_secret_sender(&recipient_ipk); + + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[0, 2], + &produce_random_nonces(1), + &[(recipient_npk.clone(), shared_secret_recipient.clone())], + &[], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![sender_address], + vec![sender_acc.nonce], + vec![( + recipient_npk.clone(), + recipient_ipk.clone(), + eph_holder_recipient.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[sender_priv_key], + ); + let tx = nssa::PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + [shared_secret_recipient], + )) + } + + pub async fn send_transfer_token_transaction_shielded_foreign_account( + &self, + sender_address: Address, + recipient_npk: NullifierPublicKey, + recipient_ipk: IncomingViewingPublicKey, + amount: u128, + ) -> Result { + let Ok(sender_acc) = self.get_account_public(sender_address).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some(sender_priv_key) = self + .storage + .user_data + .get_pub_account_signing_key(&sender_address) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let recipient_acc = nssa_core::account::Account::default(); + + let program = Program::token(); + + let sender_pre = AccountWithMetadata::new(sender_acc.clone(), true, sender_address); + let recipient_pre = AccountWithMetadata::new(recipient_acc.clone(), false, &recipient_npk); + + let eph_holder_recipient = EphemeralKeyHolder::new(&recipient_npk); + let shared_secret_recipient = + eph_holder_recipient.calculate_shared_secret_sender(&recipient_ipk); + + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[0, 2], + &produce_random_nonces(1), + &[(recipient_npk.clone(), shared_secret_recipient.clone())], + &[], + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![sender_address], + vec![sender_acc.nonce], + vec![( + recipient_npk.clone(), + recipient_ipk.clone(), + eph_holder_recipient.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[sender_priv_key], + ); + let tx = nssa::PrivacyPreservingTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_private(tx).await?) + } +}