diff --git a/examples/program_deployment/src/bin/run_hello_world_private.rs b/examples/program_deployment/src/bin/run_hello_world_private.rs index 8bbcf56f..725019f1 100644 --- a/examples/program_deployment/src/bin/run_hello_world_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_private.rs @@ -52,7 +52,6 @@ async fn main() { accounts, Program::serialize_instruction(greeting).unwrap(), &program.into(), - None, ) .await .unwrap(); diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs index 38f6158c..d68e99dc 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs @@ -60,7 +60,6 @@ async fn main() { accounts, Program::serialize_instruction(instruction).unwrap(), &program_with_dependencies, - None, ) .await .unwrap(); diff --git a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs index 3e2ebd8d..e6f667a6 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs @@ -106,7 +106,6 @@ async fn main() { accounts, Program::serialize_instruction(instruction).unwrap(), &program.into(), - None, ) .await .unwrap(); @@ -148,7 +147,6 @@ async fn main() { accounts, Program::serialize_instruction(instruction).unwrap(), &program.into(), - None, ) .await .unwrap(); diff --git a/integration_tests/tests/private_pda.rs b/integration_tests/tests/private_pda.rs index 7e3931fd..b0e3336e 100644 --- a/integration_tests/tests/private_pda.rs +++ b/integration_tests/tests/private_pda.rs @@ -139,7 +139,6 @@ async fn spend_private_pda( Program::serialize_instruction((seed, amount, auth_transfer_id)) .context("failed to serialize pda_spend_proxy instruction")?, spend_program, - None, ) .await .map_err(|e| anyhow::anyhow!("{e}"))?; diff --git a/test_fixtures/src/setup.rs b/test_fixtures/src/setup.rs index cd812841..5d7377b1 100644 --- a/test_fixtures/src/setup.rs +++ b/test_fixtures/src/setup.rs @@ -296,7 +296,6 @@ async fn claim_funds_from_vault_to_private( ], instruction_data, &program_with_dependencies, - None, ) .await .context("Failed to submit private vault claim transaction")?; diff --git a/wallet/src/account_manager.rs b/wallet/src/account_manager.rs index 5caf2b22..9a6e60e7 100644 --- a/wallet/src/account_manager.rs +++ b/wallet/src/account_manager.rs @@ -1,6 +1,7 @@ use anyhow::Result; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; -use nssa::{AccountId, PrivateKey}; +use keycard_wallet::{KeycardWallet, python_path}; +use nssa::{AccountId, PrivateKey, PublicKey, Signature}; use nssa_core::{ Identifier, InputAccountIdentity, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, @@ -15,6 +16,11 @@ pub enum AccountIdentity { Public(AccountId), /// A public account without signing. Would not try to sign, even if account is owned. PublicNoSign(AccountId), + /// A public account from keycard. Mandatory signing. + PublicKeycard { + account_id: AccountId, + key_path: String, + }, PrivateOwned(AccountId), PrivateForeign { npk: NullifierPublicKey, @@ -57,7 +63,10 @@ impl AccountIdentity { /// Note: `PublicNoSign` still counts as public, the variant just suppresses the signing-key /// lookup. pub const fn is_public(&self) -> bool { - matches!(&self, Self::Public(_) | Self::PublicNoSign(_)) + matches!( + &self, + Self::Public(_) | Self::PublicNoSign(_) | Self::PublicKeycard { .. } + ) } #[must_use] @@ -86,11 +95,16 @@ enum State { account: AccountWithMetadata, sk: Option, }, + PublicKeycard { + account: AccountWithMetadata, + key_path: String, + }, Private(AccountPreparedData), } pub struct AccountManager { states: Vec, + pin: Option, } impl AccountManager { @@ -99,6 +113,7 @@ impl AccountManager { accounts: Vec, ) -> Result { let mut states = Vec::with_capacity(accounts.len()); + let mut pin = None; for account in accounts { let state = match account { @@ -124,6 +139,35 @@ impl AccountManager { State::Public { account, sk } } + AccountIdentity::PublicKeycard { + account_id, + key_path, + } => { + let acc = wallet + .get_account_public(account_id) + .await + .map_err(ExecutionFailureKind::SequencerError)?; + + let account = AccountWithMetadata::new(acc.clone(), true, account_id); + + if pin.is_none() { + pin = Some( + crate::helperfunctions::read_pin() + .map_err(|e| { + ExecutionFailureKind::KeycardError(pyo3::PyErr::new::< + pyo3::exceptions::PyRuntimeError, + _, + >( + e.to_string() + )) + })? + .as_str() + .to_owned(), + ); + } + + State::PublicKeycard { account, key_path } + } AccountIdentity::PrivateOwned(account_id) => { let pre = private_key_tree_acc_preparation(wallet, account_id, false).await?; @@ -214,14 +258,16 @@ impl AccountManager { states.push(state); } - Ok(Self { states }) + Ok(Self { states, pin }) } pub fn pre_states(&self) -> Vec { self.states .iter() .map(|state| match state { - State::Public { account, .. } => account.clone(), + State::Public { account, .. } | State::PublicKeycard { account, .. } => { + account.clone() + } State::Private(pre) => pre.pre_state.clone(), }) .collect() @@ -232,6 +278,7 @@ impl AccountManager { .iter() .filter_map(|state| match state { State::Public { account, sk } => sk.as_ref().map(|_| account.account.nonce), + State::PublicKeycard { account, .. } => Some(account.account.nonce), State::Private(_) => None, }) .collect() @@ -247,7 +294,7 @@ impl AccountManager { vpk: pre.vpk.clone(), epk: pre.epk.clone(), }), - State::Public { .. } => None, + State::Public { .. } | State::PublicKeycard { .. } => None, }) .collect() } @@ -260,7 +307,7 @@ impl AccountManager { self.states .iter() .map(|state| match state { - State::Public { .. } => InputAccountIdentity::Public, + State::Public { .. } | State::PublicKeycard { .. } => InputAccountIdentity::Public, State::Private(pre) if pre.is_pda => match (pre.nsk, pre.proof.clone()) { (Some(nsk), Some(membership_proof)) => InputAccountIdentity::PrivatePdaUpdate { ssk: pre.ssk, @@ -304,21 +351,66 @@ impl AccountManager { self.states .iter() .filter_map(|state| match state { - State::Public { account, .. } => Some(account.account_id), + State::Public { account, .. } | State::PublicKeycard { account, .. } => { + Some(account.account_id) + } State::Private(_) => None, }) .collect() } - pub fn public_account_auth(&self) -> Vec<&PrivateKey> { + pub fn public_non_keycard_account_auth(&self) -> Vec<&PrivateKey> { self.states .iter() .filter_map(|state| match state { State::Public { sk, .. } => sk.as_ref(), - State::Private(_) => None, + State::PublicKeycard { .. } | State::Private(_) => None, }) .collect() } + + pub fn sign_message(&self, message_hash: [u8; 32]) -> Result> { + let mut sigs: Vec<(Signature, PublicKey)> = self + .public_non_keycard_account_auth() + .into_iter() + .map(|key| { + ( + Signature::new(key, &message_hash), + PublicKey::new_from_private_key(key), + ) + }) + .collect(); + + let keycard_paths = self + .states + .iter() + .fold(vec![], |mut acc, state| match state { + State::Private(_) | State::Public { .. } => acc, + State::PublicKeycard { + account: _, + key_path, + } => { + acc.push(key_path.as_str()); + acc + } + }); + + if let Some(pin) = self.pin.clone() { + pyo3::Python::with_gil(|py| -> pyo3::PyResult<()> { + python_path::add_python_path(py)?; + let wallet = KeycardWallet::new(py)?; + wallet.connect(py, &pin)?; + for path in keycard_paths { + sigs.push(wallet.sign_message_for_path(py, path, &message_hash)?); + } + drop(wallet.close_session(py)); + Ok(()) + }) + .map_err(anyhow::Error::from)?; + } + + Ok(sigs) + } } struct AccountPreparedData { diff --git a/wallet/src/cli/programs/amm.rs b/wallet/src/cli/programs/amm.rs index f2d546c6..6b1a657c 100644 --- a/wallet/src/cli/programs/amm.rs +++ b/wallet/src/cli/programs/amm.rs @@ -259,7 +259,6 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand { max_amount_b, &user_holding_a, &user_holding_b, - &user_holding_lp, ) .await?; println!("Transaction hash is {tx_hash}"); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 623171fb..4978c13b 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -16,20 +16,15 @@ use bip39::Mnemonic; use common::{HashType, transaction::NSSATransaction}; use config::WalletConfig; use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use keycard_wallet::KeycardWallet; use log::info; use nssa::{ - Account, AccountId, PrivacyPreservingTransaction, PublicKey, PublicTransaction, Signature, + Account, AccountId, PrivacyPreservingTransaction, privacy_preserving_transaction::{ circuit::ProgramWithDependencies, message::EncryptedAccountData, }, - program::Program, - public_transaction::WitnessSet as PublicWitnessSet, }; use nssa_core::{ - Commitment, MembershipProof, SharedSecretKey, - account::{AccountWithMetadata, Nonce}, - program::InstructionData, + Commitment, MembershipProof, SharedSecretKey, account::Nonce, program::InstructionData, }; use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder}; use storage::Storage; @@ -37,10 +32,8 @@ use tokio::io::AsyncWriteExt as _; use crate::{ account::{AccountIdWithPrivacy, Label}, - cli::CliAccountMention, config::WalletConfigOverrides, poller::TxPoller, - signing::SigningGroup, storage::key_chain::SharedAccountEntry, }; @@ -561,76 +554,15 @@ impl WalletCore { Ok(()) } - /// Send a public transaction, fetching nonces automatically from - /// [`SigningGroup::signing_ids`]. - pub async fn send_public_tx( - &self, - program: &Program, - account_ids: Vec, - instruction: T, - groups: SigningGroup, - ) -> Result { - let nonces = self - .get_accounts_nonces(groups.signing_ids()) - .await - .map_err(ExecutionFailureKind::SequencerError)?; - self.send_public_tx_with_nonces(program, account_ids, nonces, instruction, groups) - .await - } - - /// Send a public transaction with caller-supplied nonces. - /// - /// Use this when the caller needs to assemble or augment nonces before submission - /// (e.g. injecting a keycard account nonce that was fetched separately). - pub async fn send_public_tx_with_nonces( - &self, - program: &Program, - account_ids: Vec, - nonces: Vec, - instruction: T, - groups: SigningGroup, - ) -> Result { - let message = nssa::public_transaction::Message::try_new( - program.id(), - account_ids, - nonces, - instruction, - )?; - - let pin = if groups.needs_pin() { - crate::helperfunctions::read_pin() - .map_err(ExecutionFailureKind::from_anyhow)? - .as_str() - .to_owned() - } else { - String::new() - }; - - let sigs = groups - .sign_all(&message.hash(), &pin) - .map_err(ExecutionFailureKind::from_anyhow)?; - - let tx = PublicTransaction::new(message, PublicWitnessSet::from_raw_parts(sigs)); - Ok(self - .sequencer_client - .send_transaction(NSSATransaction::Public(tx)) - .await?) - } - pub async fn send_privacy_preserving_tx( &self, accounts: Vec, instruction_data: InstructionData, program: &ProgramWithDependencies, - mention: Option<&CliAccountMention>, ) -> Result<(HashType, Vec), ExecutionFailureKind> { - self.send_privacy_preserving_tx_with_pre_check( - accounts, - instruction_data, - program, - |_| Ok(()), - mention, - ) + self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| { + Ok(()) + }) .await } @@ -640,66 +572,10 @@ impl WalletCore { instruction_data: InstructionData, program: &ProgramWithDependencies, tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, - mention: Option<&CliAccountMention>, ) -> Result<(HashType, Vec), ExecutionFailureKind> { let acc_manager = account_manager::AccountManager::new(self, accounts).await?; - let mut pre_states = acc_manager.pre_states(); - - let (keycard_account, keycard_pin, keycard_path) = if let Some(key_path_str) = - mention.and_then(CliAccountMention::key_path) - { - let pin = crate::helperfunctions::read_pin().map_err(|e| { - ExecutionFailureKind::KeycardError(pyo3::PyErr::new::< - pyo3::exceptions::PyRuntimeError, - _, - >(e.to_string())) - })?; - let account_id_str = - KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path_str)?; - let account_id: AccountId = match account_id_str - .parse::() - .expect("`wallet::lib::send_privacy_preserving_tx_with_pre_check`: invalid account id parsed") - { - AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id, - }; - let account = self - .get_account_public(account_id) - .await - .expect("`wallet::lib::send_privacy_preserving_tx_with_pre_check`: unable to retrieve public account"); - let pin_str = pin.as_str().to_owned(); - ( - Some(AccountWithMetadata { - account, - is_authorized: true, - account_id, - }), - Some(pin_str), - Some(key_path_str.to_owned()), - ) - } else { - (None, None, None) - }; - - let mut nonces: Vec = acc_manager.public_account_nonces().into_iter().collect(); - - let mut account_ids: Vec = acc_manager.public_account_ids(); - - if let Some(acc) = keycard_account.as_ref() { - if acc_manager.public_account_ids().contains(&acc.account_id) { - if let Some(pre) = pre_states - .iter_mut() - .find(|p| p.account_id == acc.account_id) - { - pre.is_authorized = true; - } - nonces.push(acc.account.nonce); - } else { - nonces.push(acc.account.nonce); - account_ids.push(acc.account_id); - pre_states.push(acc.clone()); - } - } + let pre_states = acc_manager.pre_states(); tx_pre_check( &pre_states @@ -714,54 +590,30 @@ impl WalletCore { instruction_data, acc_manager.account_identities(), &program.to_owned(), - ) - .unwrap(); + )?; let message = nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( - account_ids, - nonces, + acc_manager.public_account_ids(), + acc_manager.public_account_nonces(), private_account_keys .iter() .map(|keys| (keys.npk, keys.vpk.clone(), keys.epk.clone())) .collect(), output, - ) - .unwrap(); + )?; + + let message_hash = message.hash(); + let signatures_public_keys = acc_manager + .sign_message(message_hash) + .map_err(ExecutionFailureKind::from_anyhow)?; let witness_set = - if let (Some(pin), Some(path)) = (keycard_pin.as_deref(), keycard_path.as_deref()) { - let hash = message.hash(); - let local_auth = acc_manager.public_account_auth(); - let mut sigs: Vec<(Signature, PublicKey)> = local_auth - .iter() - .map(|&key| { - ( - Signature::new(key, &hash), - PublicKey::new_from_private_key(key), - ) - }) - .collect(); - let keycard_sig = pyo3::Python::with_gil(|py| { - let mut ctx = crate::signing::KeycardSessionContext::new(pin); - let result = ctx - .get_or_connect(py) - .and_then(|w| w.sign_message_for_path(py, path, &hash)); - ctx.close(py); - result - }) - .map_err(ExecutionFailureKind::KeycardError)?; - sigs.push(keycard_sig); - nssa::privacy_preserving_transaction::witness_set::WitnessSet::from_raw_parts( - sigs, proof, - ) - } else { - nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( - &message, - proof, - &acc_manager.public_account_auth(), - ) - }; + nssa::privacy_preserving_transaction::witness_set::WitnessSet::from_raw_parts( + signatures_public_keys, + proof, + ); + let tx = PrivacyPreservingTransaction::new(message, witness_set); let shared_secrets: Vec<_> = private_account_keys @@ -816,7 +668,6 @@ impl WalletCore { let account_ids = acc_manager.public_account_ids(); let program_id = program.program.id(); let nonces = acc_manager.public_account_nonces(); - let private_keys = acc_manager.public_account_auth(); let message = nssa::public_transaction::Message::new_preserialized( program_id, @@ -825,8 +676,13 @@ impl WalletCore { instruction_data, ); + let message_hash = message.hash(); + let signatures_public_keys = acc_manager + .sign_message(message_hash) + .map_err(ExecutionFailureKind::from_anyhow)?; + let witness_set = - nssa::public_transaction::WitnessSet::for_message(&message, &private_keys); + nssa::public_transaction::WitnessSet::from_raw_parts(signatures_public_keys); let tx = nssa::public_transaction::PublicTransaction::new(message, witness_set); diff --git a/wallet/src/program_facades/amm.rs b/wallet/src/program_facades/amm.rs index 9aa25661..d60039a6 100644 --- a/wallet/src/program_facades/amm.rs +++ b/wallet/src/program_facades/amm.rs @@ -15,10 +15,34 @@ impl Amm<'_> { user_holding_lp: AccountId, balance_a: u128, balance_b: u128, - _a_mention: &CliAccountMention, - _b_mention: &CliAccountMention, - _lp_mention: &CliAccountMention, + user_holding_a_mention: &CliAccountMention, + user_holding_b_mention: &CliAccountMention, + user_holding_lp_mention: &CliAccountMention, ) -> Result { + let user_holding_a_identity = user_holding_a_mention.key_path().map_or( + AccountIdentity::Public(user_holding_a), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_a, + key_path: key_path.to_owned(), + }, + ); + + let user_holding_b_identity = user_holding_b_mention.key_path().map_or( + AccountIdentity::Public(user_holding_b), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_b, + key_path: key_path.to_owned(), + }, + ); + + let user_holding_lp_identity = user_holding_lp_mention.key_path().map_or( + AccountIdentity::Public(user_holding_lp), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_lp, + key_path: key_path.to_owned(), + }, + ); + let program = Program::amm(); let amm_program_id = Program::amm().id(); let user_a_acc = self @@ -59,9 +83,9 @@ impl Amm<'_> { AccountIdentity::PublicNoSign(vault_holding_a), AccountIdentity::PublicNoSign(vault_holding_b), AccountIdentity::PublicNoSign(pool_lp), - AccountIdentity::Public(user_holding_a), - AccountIdentity::Public(user_holding_b), - AccountIdentity::Public(user_holding_lp), + user_holding_a_identity, + user_holding_b_identity, + user_holding_lp_identity, ], instruction_data, &program.into(), @@ -77,8 +101,8 @@ impl Amm<'_> { swap_amount_in: u128, min_amount_out: u128, token_definition_id_in: AccountId, - _a_mention: &CliAccountMention, - _b_mention: &CliAccountMention, + user_holding_a_mention: &CliAccountMention, + user_holding_b_mention: &CliAccountMention, ) -> Result { let program = Program::amm(); let amm_program_id = Program::amm().id(); @@ -121,13 +145,25 @@ impl Amm<'_> { } let user_a_signing_identity = if token_definition_id_in == definition_token_a_id { - AccountIdentity::Public(user_holding_a) + user_holding_a_mention.key_path().map_or( + AccountIdentity::Public(user_holding_a), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_a, + key_path: key_path.to_owned(), + }, + ) } else { AccountIdentity::PublicNoSign(user_holding_a) }; let user_b_signing_identity = if token_definition_id_in == definition_token_b_id { - AccountIdentity::Public(user_holding_b) + user_holding_b_mention.key_path().map_or( + AccountIdentity::Public(user_holding_b), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_b, + key_path: key_path.to_owned(), + }, + ) } else { AccountIdentity::PublicNoSign(user_holding_b) }; @@ -155,8 +191,8 @@ impl Amm<'_> { exact_amount_out: u128, max_amount_in: u128, token_definition_id_in: AccountId, - _a_mention: &CliAccountMention, - _b_mention: &CliAccountMention, + user_holding_a_mention: &CliAccountMention, + user_holding_b_mention: &CliAccountMention, ) -> Result { let program = Program::amm(); let amm_program_id = Program::amm().id(); @@ -199,13 +235,25 @@ impl Amm<'_> { } let user_a_signing_identity = if token_definition_id_in == definition_token_a_id { - AccountIdentity::Public(user_holding_a) + user_holding_a_mention.key_path().map_or( + AccountIdentity::Public(user_holding_a), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_a, + key_path: key_path.to_owned(), + }, + ) } else { AccountIdentity::PublicNoSign(user_holding_a) }; let user_b_signing_identity = if token_definition_id_in == definition_token_b_id { - AccountIdentity::Public(user_holding_b) + user_holding_b_mention.key_path().map_or( + AccountIdentity::Public(user_holding_b), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_b, + key_path: key_path.to_owned(), + }, + ) } else { AccountIdentity::PublicNoSign(user_holding_b) }; @@ -234,10 +282,25 @@ impl Amm<'_> { min_amount_liquidity: u128, max_amount_to_add_token_a: u128, max_amount_to_add_token_b: u128, - _a_mention: &CliAccountMention, - _b_mention: &CliAccountMention, - _lp_mention: &CliAccountMention, + user_holding_a_mention: &CliAccountMention, + user_holding_b_mention: &CliAccountMention, ) -> Result { + let user_holding_a_identity = user_holding_a_mention.key_path().map_or( + AccountIdentity::Public(user_holding_a), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_a, + key_path: key_path.to_owned(), + }, + ); + + let user_holding_b_identity = user_holding_b_mention.key_path().map_or( + AccountIdentity::Public(user_holding_b), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_b, + key_path: key_path.to_owned(), + }, + ); + let program = Program::amm(); let amm_program_id = Program::amm().id(); let user_a_acc = self @@ -278,8 +341,8 @@ impl Amm<'_> { AccountIdentity::PublicNoSign(vault_holding_a), AccountIdentity::PublicNoSign(vault_holding_b), AccountIdentity::PublicNoSign(pool_lp), - AccountIdentity::Public(user_holding_a), - AccountIdentity::Public(user_holding_b), + user_holding_a_identity, + user_holding_b_identity, AccountIdentity::PublicNoSign(user_holding_lp), ], instruction_data, @@ -297,8 +360,16 @@ impl Amm<'_> { remove_liquidity_amount: u128, min_amount_to_remove_token_a: u128, min_amount_to_remove_token_b: u128, - _lp_mention: &CliAccountMention, + user_holding_lp_mention: &CliAccountMention, ) -> Result { + let user_holding_lp_identity = user_holding_lp_mention.key_path().map_or( + AccountIdentity::Public(user_holding_lp), + |key_path| AccountIdentity::PublicKeycard { + account_id: user_holding_lp, + key_path: key_path.to_owned(), + }, + ); + let program = Program::amm(); let amm_program_id = Program::amm().id(); let user_a_acc = self @@ -341,7 +412,7 @@ impl Amm<'_> { AccountIdentity::PublicNoSign(pool_lp), AccountIdentity::PublicNoSign(user_holding_a), AccountIdentity::PublicNoSign(user_holding_b), - AccountIdentity::Public(user_holding_lp), + user_holding_lp_identity, ], instruction_data, &program.into(), diff --git a/wallet/src/program_facades/ata.rs b/wallet/src/program_facades/ata.rs index 9852935f..d3a24fa3 100644 --- a/wallet/src/program_facades/ata.rs +++ b/wallet/src/program_facades/ata.rs @@ -16,8 +16,18 @@ impl Ata<'_> { &self, owner_id: AccountId, definition_id: AccountId, - _owner_mention: &CliAccountMention, + owner_mention: &CliAccountMention, ) -> Result { + let owner_identity = + owner_mention + .key_path() + .map_or(AccountIdentity::Public(owner_id), |key_path| { + AccountIdentity::PublicKeycard { + account_id: owner_id, + key_path: key_path.to_owned(), + } + }); + let program = Program::ata(); let ata_program_id = program.id(); let ata_id = get_associated_token_account_id( @@ -31,7 +41,7 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - AccountIdentity::Public(owner_id), + owner_identity, AccountIdentity::PublicNoSign(definition_id), AccountIdentity::PublicNoSign(ata_id), ], @@ -47,8 +57,18 @@ impl Ata<'_> { definition_id: AccountId, recipient_id: AccountId, amount: u128, - _owner_mention: &CliAccountMention, + owner_mention: &CliAccountMention, ) -> Result { + let owner_identity = + owner_mention + .key_path() + .map_or(AccountIdentity::Public(owner_id), |key_path| { + AccountIdentity::PublicKeycard { + account_id: owner_id, + key_path: key_path.to_owned(), + } + }); + let program = Program::ata(); let ata_program_id = program.id(); let sender_ata_id = get_associated_token_account_id( @@ -65,7 +85,7 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - AccountIdentity::Public(owner_id), + owner_identity, AccountIdentity::PublicNoSign(sender_ata_id), AccountIdentity::PublicNoSign(recipient_id), ], @@ -80,8 +100,18 @@ impl Ata<'_> { owner_id: AccountId, definition_id: AccountId, amount: u128, - _owner_mention: &CliAccountMention, + owner_mention: &CliAccountMention, ) -> Result { + let owner_identity = + owner_mention + .key_path() + .map_or(AccountIdentity::Public(owner_id), |key_path| { + AccountIdentity::PublicKeycard { + account_id: owner_id, + key_path: key_path.to_owned(), + } + }); + let program = Program::ata(); let ata_program_id = program.id(); let holder_ata_id = get_associated_token_account_id( @@ -98,7 +128,7 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - AccountIdentity::Public(owner_id), + owner_identity, AccountIdentity::PublicNoSign(holder_ata_id), AccountIdentity::PublicNoSign(definition_id), ], @@ -132,12 +162,7 @@ impl Ata<'_> { ]; self.0 - .send_privacy_preserving_tx( - accounts, - instruction_data, - &ata_with_token_dependency(), - None, - ) + .send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency()) .await .map(|(hash, mut secrets)| { let secret = secrets.pop().expect("expected owner's secret"); @@ -174,12 +199,7 @@ impl Ata<'_> { ]; self.0 - .send_privacy_preserving_tx( - accounts, - instruction_data, - &ata_with_token_dependency(), - None, - ) + .send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency()) .await .map(|(hash, mut secrets)| { let secret = secrets.pop().expect("expected owner's secret"); @@ -215,12 +235,7 @@ impl Ata<'_> { ]; self.0 - .send_privacy_preserving_tx( - accounts, - instruction_data, - &ata_with_token_dependency(), - None, - ) + .send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency()) .await .map(|(hash, mut secrets)| { let secret = secrets.pop().expect("expected owner's secret"); diff --git a/wallet/src/program_facades/native_token_transfer/deshielded.rs b/wallet/src/program_facades/native_token_transfer/deshielded.rs index 76df074c..31374f99 100644 --- a/wallet/src/program_facades/native_token_transfer/deshielded.rs +++ b/wallet/src/program_facades/native_token_transfer/deshielded.rs @@ -24,7 +24,6 @@ impl NativeTokenTransfer<'_> { instruction_data, &program.into(), tx_pre_check, - None, ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index eb10c283..481e4a5f 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -24,7 +24,6 @@ impl NativeTokenTransfer<'_> { vec![account], Program::serialize_instruction(instruction).unwrap(), &Program::authenticated_transfer_program().into(), - None, ) .await .map(|(resp, secrets)| { @@ -59,7 +58,6 @@ impl NativeTokenTransfer<'_> { instruction_data, &program.into(), tx_pre_check, - None, ) .await .map(|(resp, secrets)| { @@ -93,7 +91,6 @@ impl NativeTokenTransfer<'_> { instruction_data, &program.into(), tx_pre_check, - None, ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/program_facades/native_token_transfer/public.rs b/wallet/src/program_facades/native_token_transfer/public.rs index 68c8e0be..8d441b2b 100644 --- a/wallet/src/program_facades/native_token_transfer/public.rs +++ b/wallet/src/program_facades/native_token_transfer/public.rs @@ -3,7 +3,10 @@ use common::HashType; use nssa::{AccountId, program::Program}; use super::NativeTokenTransfer; -use crate::{ExecutionFailureKind, cli::CliAccountMention, signing::SigningGroup}; +use crate::{ + AccountIdentity, ExecutionFailureKind, cli::CliAccountMention, + program_facades::native_token_transfer::auth_transfer_preparation, +}; impl NativeTokenTransfer<'_> { pub async fn send_public_transfer( @@ -14,20 +17,33 @@ impl NativeTokenTransfer<'_> { from_mention: &CliAccountMention, to_mention: &CliAccountMention, ) -> Result { - let mut groups = SigningGroup::new(); - groups - .add_required(from_mention, from, self.0) - .and_then(|()| groups.add_optional(to_mention, to, self.0)) - .map_err(ExecutionFailureKind::from_anyhow)?; + let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); + + let from_identity = + from_mention + .key_path() + .map_or(AccountIdentity::Public(from), |key_path| { + AccountIdentity::PublicKeycard { + account_id: from, + key_path: key_path.to_owned(), + } + }); + + let to_identity = to_mention + .key_path() + .map_or(AccountIdentity::Public(to), |key_path| { + AccountIdentity::PublicKeycard { + account_id: to, + key_path: key_path.to_owned(), + } + }); self.0 - .send_public_tx( - &Program::authenticated_transfer_program(), - vec![from, to], - AuthTransferInstruction::Transfer { - amount: balance_to_move, - }, - groups, + .send_pub_tx_with_pre_check( + vec![from_identity, to_identity], + instruction_data, + &program.into(), + tx_pre_check, ) .await } @@ -37,18 +53,21 @@ impl NativeTokenTransfer<'_> { from: AccountId, account_mention: &CliAccountMention, ) -> Result { - let mut groups = SigningGroup::new(); - groups - .add_required(account_mention, from, self.0) - .map_err(ExecutionFailureKind::from_anyhow)?; + let from_identity = + account_mention + .key_path() + .map_or(AccountIdentity::Public(from), |key_path| { + AccountIdentity::PublicKeycard { + account_id: from, + key_path: key_path.to_owned(), + } + }); + + let program = Program::authenticated_transfer_program(); + let instruction_data = Program::serialize_instruction(AuthTransferInstruction::Initialize)?; self.0 - .send_public_tx( - &Program::authenticated_transfer_program(), - vec![from], - AuthTransferInstruction::Initialize, - groups, - ) + .send_pub_tx(vec![from_identity], instruction_data, &program.into()) .await } } diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs index ffe02a30..339673fb 100644 --- a/wallet/src/program_facades/native_token_transfer/shielded.rs +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -13,11 +13,21 @@ impl NativeTokenTransfer<'_> { balance_to_move: u128, from_mention: &CliAccountMention, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { + let from_identity = + from_mention + .key_path() + .map_or(AccountIdentity::Public(from), |key_path| { + AccountIdentity::PublicKeycard { + account_id: from, + key_path: key_path.to_owned(), + } + }); + let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 .send_privacy_preserving_tx_with_pre_check( vec![ - AccountIdentity::Public(from), + from_identity, self.0 .resolve_private_account(to) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, @@ -25,7 +35,6 @@ impl NativeTokenTransfer<'_> { instruction_data, &program.into(), tx_pre_check, - Some(from_mention), ) .await .map(|(resp, secrets)| { @@ -46,11 +55,21 @@ impl NativeTokenTransfer<'_> { balance_to_move: u128, from_mention: &CliAccountMention, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { + let from_identity = + from_mention + .key_path() + .map_or(AccountIdentity::Public(from), |key_path| { + AccountIdentity::PublicKeycard { + account_id: from, + key_path: key_path.to_owned(), + } + }); + let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 .send_privacy_preserving_tx_with_pre_check( vec![ - AccountIdentity::Public(from), + from_identity, AccountIdentity::PrivateForeign { npk: to_npk, vpk: to_vpk, @@ -60,7 +79,6 @@ impl NativeTokenTransfer<'_> { instruction_data, &program.into(), tx_pre_check, - Some(from_mention), ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs index 0cd6d160..2e40e78b 100644 --- a/wallet/src/program_facades/pinata.rs +++ b/wallet/src/program_facades/pinata.rs @@ -62,7 +62,6 @@ impl Pinata<'_> { ], nssa::program::Program::serialize_instruction(solution).unwrap(), &nssa::program::Program::pinata().into(), - None, ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index 64cb2a2e..9d327f48 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -14,9 +14,25 @@ impl Token<'_> { supply_account_id: AccountId, name: String, total_supply: u128, - _definition_mention: &CliAccountMention, - _supply_mention: &CliAccountMention, + definition_mention: &CliAccountMention, + supply_mention: &CliAccountMention, ) -> Result { + let definition_identity = definition_mention.key_path().map_or( + AccountIdentity::Public(definition_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: definition_account_id, + key_path: key_path.to_owned(), + }, + ); + + let supply_identity = supply_mention.key_path().map_or( + AccountIdentity::Public(supply_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: supply_account_id, + key_path: key_path.to_owned(), + }, + ); + let program = Program::token(); let instruction = Instruction::NewFungibleDefinition { name, total_supply }; let instruction_data = @@ -24,10 +40,7 @@ impl Token<'_> { self.0 .send_pub_tx( - vec![ - AccountIdentity::Public(definition_account_id), - AccountIdentity::Public(supply_account_id), - ], + vec![definition_identity, supply_identity], instruction_data, &program.into(), ) @@ -55,7 +68,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -88,7 +100,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -123,7 +134,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -139,9 +149,25 @@ impl Token<'_> { sender_account_id: AccountId, recipient_account_id: AccountId, amount: u128, - _sender_mention: &CliAccountMention, - _recipient_mention: &CliAccountMention, + sender_mention: &CliAccountMention, + recipient_mention: &CliAccountMention, ) -> Result { + let sender_identity = sender_mention.key_path().map_or( + AccountIdentity::Public(sender_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: sender_account_id, + key_path: key_path.to_owned(), + }, + ); + + let recipient_identity = recipient_mention.key_path().map_or( + AccountIdentity::Public(recipient_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: recipient_account_id, + key_path: key_path.to_owned(), + }, + ); + let program = Program::token(); let instruction = Instruction::Transfer { amount_to_transfer: amount, @@ -151,10 +177,7 @@ impl Token<'_> { self.0 .send_pub_tx( - vec![ - AccountIdentity::Public(sender_account_id), - AccountIdentity::Public(recipient_account_id), - ], + vec![sender_identity, recipient_identity], instruction_data, &program.into(), ) @@ -185,7 +208,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -224,7 +246,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -257,7 +278,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -276,6 +296,14 @@ impl Token<'_> { amount: u128, sender_mention: &CliAccountMention, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { + let sender_identity = sender_mention.key_path().map_or( + AccountIdentity::Public(sender_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: sender_account_id, + key_path: key_path.to_owned(), + }, + ); + let instruction = Instruction::Transfer { amount_to_transfer: amount, }; @@ -284,14 +312,13 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountIdentity::Public(sender_account_id), + sender_identity, self.0 .resolve_private_account(recipient_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, &Program::token().into(), - Some(sender_mention), ) .await .map(|(resp, secrets)| { @@ -312,6 +339,14 @@ impl Token<'_> { amount: u128, sender_mention: &CliAccountMention, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { + let sender_identity = sender_mention.key_path().map_or( + AccountIdentity::Public(sender_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: sender_account_id, + key_path: key_path.to_owned(), + }, + ); + let instruction = Instruction::Transfer { amount_to_transfer: amount, }; @@ -320,7 +355,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountIdentity::Public(sender_account_id), + sender_identity, AccountIdentity::PrivateForeign { npk: recipient_npk, vpk: recipient_vpk, @@ -329,7 +364,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - Some(sender_mention), ) .await .map(|(resp, secrets)| { @@ -346,8 +380,16 @@ impl Token<'_> { definition_account_id: AccountId, holder_account_id: AccountId, amount: u128, - _holder_mention: &CliAccountMention, + holder_mention: &CliAccountMention, ) -> Result { + let holder_identity = holder_mention.key_path().map_or( + AccountIdentity::Public(holder_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: holder_account_id, + key_path: key_path.to_owned(), + }, + ); + let program = Program::token(); let instruction = Instruction::Burn { amount_to_burn: amount, @@ -359,7 +401,7 @@ impl Token<'_> { .send_pub_tx( vec![ AccountIdentity::PublicNoSign(definition_account_id), - AccountIdentity::Public(holder_account_id), + holder_identity, ], instruction_data, &program.into(), @@ -391,7 +433,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -424,7 +465,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -458,7 +498,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -475,9 +514,25 @@ impl Token<'_> { definition_account_id: AccountId, holder_account_id: AccountId, amount: u128, - _definition_mention: &CliAccountMention, - _holder_mention: &CliAccountMention, + definition_mention: &CliAccountMention, + holder_mention: &CliAccountMention, ) -> Result { + let definition_identity = definition_mention.key_path().map_or( + AccountIdentity::Public(definition_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: definition_account_id, + key_path: key_path.to_owned(), + }, + ); + + let holder_identity = holder_mention.key_path().map_or( + AccountIdentity::Public(holder_account_id), + |key_path| AccountIdentity::PublicKeycard { + account_id: holder_account_id, + key_path: key_path.to_owned(), + }, + ); + let program = Program::token(); let instruction = Instruction::Mint { amount_to_mint: amount, @@ -487,10 +542,7 @@ impl Token<'_> { self.0 .send_pub_tx( - vec![ - AccountIdentity::Public(definition_account_id), - AccountIdentity::Public(holder_account_id), - ], + vec![definition_identity, holder_identity], instruction_data, &program.into(), ) @@ -521,7 +573,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -560,7 +611,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -593,7 +643,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -627,7 +676,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { @@ -665,7 +713,6 @@ impl Token<'_> { ], instruction_data, &Program::token().into(), - None, ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/signing.rs b/wallet/src/signing.rs index 47dd9ec1..661bbdb0 100644 --- a/wallet/src/signing.rs +++ b/wallet/src/signing.rs @@ -1,119 +1,6 @@ -use anyhow::Result; use keycard_wallet::{KeycardWallet, python_path}; -use nssa::{AccountId, PrivateKey, PublicKey, Signature}; use pyo3::Python; -use crate::{WalletCore, cli::CliAccountMention}; - -/// Groups transaction signers by type to minimise Python GIL acquisition. -/// -/// Local signers are signed in pure Rust; all keycard signers share a single Python session -/// with one `connect` / `close_session` pair. -#[derive(Default)] -pub struct SigningGroup { - local: Vec<(AccountId, PrivateKey)>, - keycard: Vec<(AccountId, String)>, -} - -impl SigningGroup { - #[must_use] - pub fn new() -> Self { - Self::default() - } - - /// Add a sender. Keycard paths are queued for the hardware session; local accounts - /// have their signing key resolved eagerly. Errors if no key is found. - pub fn add_required( - &mut self, - mention: &CliAccountMention, - account_id: AccountId, - wallet_core: &WalletCore, - ) -> Result<()> { - if let CliAccountMention::KeyPath(path) = mention { - self.keycard.push((account_id, path.clone())); - return Ok(()); - } - let key = wallet_core - .storage() - .key_chain() - .pub_account_signing_key(account_id) - .ok_or_else(|| anyhow::anyhow!("signing key not found for account {account_id}"))? - .clone(); - self.local.push((account_id, key)); - Ok(()) - } - - /// Add a recipient. Same as [`add_required`] but silently skips accounts with no local - /// key and no keycard path — they are foreign and require neither a signature nor a nonce. - pub fn add_optional( - &mut self, - mention: &CliAccountMention, - account_id: AccountId, - wallet_core: &WalletCore, - ) -> Result<()> { - if let CliAccountMention::KeyPath(path) = mention { - self.keycard.push((account_id, path.clone())); - return Ok(()); - } - if let Some(key) = wallet_core - .storage() - .key_chain() - .pub_account_signing_key(account_id) - { - self.local.push((account_id, key.clone())); - } - Ok(()) - } - - /// Returns `true` when a PIN is required (at least one keycard signer is present). - #[must_use] - pub const fn needs_pin(&self) -> bool { - !self.keycard.is_empty() - } - - /// Account IDs that require a nonce (every non-foreign signer). - #[must_use] - pub fn signing_ids(&self) -> Vec { - self.local - .iter() - .map(|(id, _)| *id) - .chain(self.keycard.iter().map(|(id, _)| *id)) - .collect() - } - - /// Sign `hash` for every account in the group. - /// - /// Local accounts are signed in pure Rust. Keycard accounts share one Python session. - pub fn sign_all(&self, hash: &[u8; 32], pin: &str) -> Result> { - let mut sigs: Vec<(Signature, PublicKey)> = self - .local - .iter() - .map(|(_, key)| { - ( - Signature::new(key, hash), - PublicKey::new_from_private_key(key), - ) - }) - .collect(); - - if !self.keycard.is_empty() { - pyo3::Python::with_gil(|py| -> pyo3::PyResult<()> { - python_path::add_python_path(py)?; - let wallet = KeycardWallet::new(py)?; - wallet.connect(py, pin)?; - for (_, path) in &self.keycard { - sigs.push(wallet.sign_message_for_path(py, path, hash)?); - } - drop(wallet.close_session(py)); - Ok(()) - }) - .map_err(anyhow::Error::from)?; - } - - Ok(sigs) - } -} - /// Lazily opens and reuses a single Keycard session for all keycard signers in one transaction. pub struct KeycardSessionContext { pin: String,