mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-07-31 12:03:14 +00:00
feat(wallet): pad private transactions to a fixed maximum
This commit is contained in:
parent
d30490442d
commit
fd3646e127
@ -83,7 +83,6 @@ async fn private_transfer_to_foreign_account() -> Result<()> {
|
|||||||
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await;
|
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await;
|
||||||
assert_eq!(tx.message.new_commitments[0], new_commitment1);
|
assert_eq!(tx.message.new_commitments[0], new_commitment1);
|
||||||
|
|
||||||
assert_eq!(tx.message.new_commitments.len(), 2);
|
|
||||||
for commitment in tx.message.new_commitments {
|
for commitment in tx.message.new_commitments {
|
||||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||||
}
|
}
|
||||||
@ -172,7 +171,6 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
|
|||||||
.context("Failed to get private account commitment for sender")?;
|
.context("Failed to get private account commitment for sender")?;
|
||||||
assert_eq!(tx.message.new_commitments[0], sender_commitment);
|
assert_eq!(tx.message.new_commitments[0], sender_commitment);
|
||||||
|
|
||||||
assert_eq!(tx.message.new_commitments.len(), 2);
|
|
||||||
for commitment in tx.message.new_commitments {
|
for commitment in tx.message.new_commitments {
|
||||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||||
}
|
}
|
||||||
@ -303,7 +301,6 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
|
|||||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||||
|
|
||||||
// Verify commitments are in state
|
// Verify commitments are in state
|
||||||
assert_eq!(tx.message.new_commitments.len(), 2);
|
|
||||||
for commitment in tx.message.new_commitments {
|
for commitment in tx.message.new_commitments {
|
||||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,6 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
|
|||||||
.context("Failed to get private account commitment for sender")?;
|
.context("Failed to get private account commitment for sender")?;
|
||||||
assert_eq!(tx.message.new_commitments[0], new_commitment1);
|
assert_eq!(tx.message.new_commitments[0], new_commitment1);
|
||||||
|
|
||||||
assert_eq!(tx.message.new_commitments.len(), 2);
|
|
||||||
for commitment in tx.message.new_commitments {
|
for commitment in tx.message.new_commitments {
|
||||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -194,6 +194,14 @@ pub struct AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AccountManager {
|
impl AccountManager {
|
||||||
|
/// The private-account count that every privacy-preserving transaction is padded up to with
|
||||||
|
/// dummy inputs via the default interface.
|
||||||
|
///
|
||||||
|
/// The value is selected based on the largest account number per-tx currently supported
|
||||||
|
/// (it is 7 for AMM). It is recommended to reassess this value per new actively supported
|
||||||
|
/// application and that all users share the value for a larger anonymity set.
|
||||||
|
const MAX_PRIVATE_ACCOUNTS: usize = 7;
|
||||||
|
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
wallet: &WalletCore,
|
wallet: &WalletCore,
|
||||||
accounts: Vec<AccountIdentity>,
|
accounts: Vec<AccountIdentity>,
|
||||||
@ -404,6 +412,17 @@ impl AccountManager {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate the dummy inputs that pad this transaction's private-account count up to
|
||||||
|
/// `MAX_PRIVATE_ACCOUNTS`.
|
||||||
|
pub fn dummy_inputs_default(&self) -> Vec<DummyInput> {
|
||||||
|
let private_count = self
|
||||||
|
.states
|
||||||
|
.iter()
|
||||||
|
.filter(|state| matches!(state, State::Private(_)))
|
||||||
|
.count();
|
||||||
|
self.dummy_inputs(Self::MAX_PRIVATE_ACCOUNTS.saturating_sub(private_count))
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the per-account input vec for the privacy-preserving circuit. Each variant carries
|
/// Build the per-account input vec for the privacy-preserving circuit. Each variant carries
|
||||||
/// exactly the fields the circuit's code path for that account needs, with the ephemeral
|
/// exactly the fields the circuit's code path for that account needs, with the ephemeral
|
||||||
/// keys (`ssk`) drawn from the cached values that `private_account_keys` and the message
|
/// keys (`ssk`) drawn from the cached values that `private_account_keys` and the message
|
||||||
@ -700,4 +719,66 @@ mod tests {
|
|||||||
assert!(acc.is_private());
|
assert!(acc.is_private());
|
||||||
assert!(!acc.is_public());
|
assert!(!acc.is_public());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn private_state() -> State {
|
||||||
|
let npk = NullifierPublicKey([0; 32]);
|
||||||
|
let vpk = ViewingPublicKey::from_seed(&[0; 32], &[0; 32]);
|
||||||
|
let pre_state = AccountWithMetadata::new(Account::default(), false, (&npk, &vpk, 0));
|
||||||
|
State::Private(AccountPreparedData {
|
||||||
|
nsk: None,
|
||||||
|
npk,
|
||||||
|
identifier: 0,
|
||||||
|
vpk,
|
||||||
|
pre_state,
|
||||||
|
proof: None,
|
||||||
|
random_seed: [0; 32],
|
||||||
|
is_pda: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_state() -> State {
|
||||||
|
let npk = NullifierPublicKey([0; 32]);
|
||||||
|
let vpk = ViewingPublicKey::from_seed(&[0; 32], &[0; 32]);
|
||||||
|
let account = AccountWithMetadata::new(Account::default(), false, (&npk, &vpk, 0));
|
||||||
|
State::Public { account, sk: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn manager(states: Vec<State>) -> AccountManager {
|
||||||
|
AccountManager {
|
||||||
|
states,
|
||||||
|
pin: None,
|
||||||
|
dummy_commitment_root: [0; 32],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dummy_inputs_default_pads_private_count_to_max() {
|
||||||
|
let max = AccountManager::MAX_PRIVATE_ACCOUNTS;
|
||||||
|
|
||||||
|
// Empty txs get padded to the max.
|
||||||
|
assert_eq!(manager(vec![]).dummy_inputs_default().len(), max);
|
||||||
|
// In a padded transaction, the padding amount depends on
|
||||||
|
// the amount of private accounts used.
|
||||||
|
assert_eq!(
|
||||||
|
manager(vec![private_state(), private_state()])
|
||||||
|
.dummy_inputs_default()
|
||||||
|
.len(),
|
||||||
|
max - 2
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
manager(vec![private_state(), public_state(), private_state()])
|
||||||
|
.dummy_inputs_default()
|
||||||
|
.len(),
|
||||||
|
max - 2
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the private accounts in the transaction exceed the max, no padding
|
||||||
|
// is done.
|
||||||
|
let full: Vec<State> = std::iter::repeat_with(private_state).take(max).collect();
|
||||||
|
assert_eq!(manager(full).dummy_inputs_default().len(), 0);
|
||||||
|
let over: Vec<State> = std::iter::repeat_with(private_state)
|
||||||
|
.take(max + 2)
|
||||||
|
.collect();
|
||||||
|
assert_eq!(manager(over).dummy_inputs_default().len(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -749,13 +749,9 @@ impl WalletCore {
|
|||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
program: &ProgramWithDependencies,
|
program: &ProgramWithDependencies,
|
||||||
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||||
self.send_privacy_preserving_tx_with_pre_check(
|
self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| {
|
||||||
accounts,
|
Ok(())
|
||||||
instruction_data,
|
})
|
||||||
program,
|
|
||||||
|_| Ok(()),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -765,7 +761,6 @@ impl WalletCore {
|
|||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
program: &ProgramWithDependencies,
|
program: &ProgramWithDependencies,
|
||||||
tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
|
tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
|
||||||
dummy_count: usize,
|
|
||||||
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||||
let acc_manager = account_manager::AccountManager::new(self, accounts).await?;
|
let acc_manager = account_manager::AccountManager::new(self, accounts).await?;
|
||||||
|
|
||||||
@ -784,7 +779,7 @@ impl WalletCore {
|
|||||||
pre_states,
|
pre_states,
|
||||||
instruction_data,
|
instruction_data,
|
||||||
acc_manager.account_identities(),
|
acc_manager.account_identities(),
|
||||||
acc_manager.dummy_inputs(dummy_count),
|
acc_manager.dummy_inputs_default(),
|
||||||
&program.to_owned(),
|
&program.to_owned(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,6 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
|
|||||||
@ -58,7 +58,6 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -92,7 +91,6 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
|
|||||||
@ -24,7 +24,6 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
@ -58,7 +57,6 @@ impl NativeTokenTransfer<'_> {
|
|||||||
instruction_data,
|
instruction_data,
|
||||||
&program.into(),
|
&program.into(),
|
||||||
tx_pre_check,
|
tx_pre_check,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|(resp, secrets)| {
|
.map(|(resp, secrets)| {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user