feat!(wallet): SigningGroup merged with AccountManager

This commit is contained in:
Pravdyvy 2026-05-27 14:56:47 +03:00
parent 89bf1e9422
commit 675fd35664
17 changed files with 407 additions and 414 deletions

View File

@ -52,7 +52,6 @@ async fn main() {
accounts,
Program::serialize_instruction(greeting).unwrap(),
&program.into(),
None,
)
.await
.unwrap();

View File

@ -60,7 +60,6 @@ async fn main() {
accounts,
Program::serialize_instruction(instruction).unwrap(),
&program_with_dependencies,
None,
)
.await
.unwrap();

View File

@ -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();

View File

@ -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}"))?;

View File

@ -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")?;

View File

@ -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<PrivateKey>,
},
PublicKeycard {
account: AccountWithMetadata,
key_path: String,
},
Private(AccountPreparedData),
}
pub struct AccountManager {
states: Vec<State>,
pin: Option<String>,
}
impl AccountManager {
@ -99,6 +113,7 @@ impl AccountManager {
accounts: Vec<AccountIdentity>,
) -> Result<Self, ExecutionFailureKind> {
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<AccountWithMetadata> {
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<Vec<(Signature, PublicKey)>> {
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 {

View File

@ -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}");

View File

@ -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<T: serde::Serialize>(
&self,
program: &Program,
account_ids: Vec<AccountId>,
instruction: T,
groups: SigningGroup,
) -> Result<HashType, ExecutionFailureKind> {
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<T: serde::Serialize>(
&self,
program: &Program,
account_ids: Vec<AccountId>,
nonces: Vec<Nonce>,
instruction: T,
groups: SigningGroup,
) -> Result<HashType, ExecutionFailureKind> {
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<AccountIdentity>,
instruction_data: InstructionData,
program: &ProgramWithDependencies,
mention: Option<&CliAccountMention>,
) -> Result<(HashType, Vec<SharedSecretKey>), 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<SharedSecretKey>), 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::<AccountIdWithPrivacy>()
.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<Nonce> = acc_manager.public_account_nonces().into_iter().collect();
let mut account_ids: Vec<AccountId> = 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);

View File

@ -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<HashType, ExecutionFailureKind> {
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<HashType, ExecutionFailureKind> {
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<HashType, ExecutionFailureKind> {
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<HashType, ExecutionFailureKind> {
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<HashType, ExecutionFailureKind> {
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(),

View File

@ -16,8 +16,18 @@ impl Ata<'_> {
&self,
owner_id: AccountId,
definition_id: AccountId,
_owner_mention: &CliAccountMention,
owner_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
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<HashType, ExecutionFailureKind> {
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<HashType, ExecutionFailureKind> {
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");

View File

@ -24,7 +24,6 @@ impl NativeTokenTransfer<'_> {
instruction_data,
&program.into(),
tx_pre_check,
None,
)
.await
.map(|(resp, secrets)| {

View File

@ -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)| {

View File

@ -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<HashType, ExecutionFailureKind> {
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<HashType, ExecutionFailureKind> {
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
}
}

View File

@ -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)| {

View File

@ -62,7 +62,6 @@ impl Pinata<'_> {
],
nssa::program::Program::serialize_instruction(solution).unwrap(),
&nssa::program::Program::pinata().into(),
None,
)
.await
.map(|(resp, secrets)| {

View File

@ -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<HashType, ExecutionFailureKind> {
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<HashType, 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 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<HashType, ExecutionFailureKind> {
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<HashType, ExecutionFailureKind> {
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)| {

View File

@ -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<AccountId> {
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<Vec<(Signature, PublicKey)>> {
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,