feat(wallet): construct and submit dummy inputs

Ciphers submitted currently have no data
This commit is contained in:
Artem Gureev 2026-07-03 15:04:52 +00:00 committed by agureev
parent 8c7f3764a5
commit 3d95594b4f
8 changed files with 81 additions and 20 deletions

View File

@ -18,8 +18,7 @@ fn main() {
program_outputs,
);
let output =
output::compute_circuit_output(execution_state, &account_identities, dummy_inputs);
let output = output::compute_circuit_output(execution_state, &account_identities, dummy_inputs);
env::commit(&output);
}

View File

@ -1,7 +1,7 @@
use lee_core::{
Commitment, CommitmentSetDigest, DummyInput, EncryptedAccountData, EncryptionScheme, EphemeralSecretKey,
InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey,
PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey,
Commitment, CommitmentSetDigest, DummyInput, EncryptedAccountData, EncryptionScheme,
EphemeralSecretKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey,
NullifierSecretKey, PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey,
account::{Account, AccountId, Nonce},
compute_digest_for_path,
encryption::{ViewTag, ViewingPublicKey},
@ -273,7 +273,9 @@ pub fn compute_circuit_output(
fn emit_dummy_output(output: &mut PrivacyPreservingCircuitOutput, dummy: DummyInput) {
let nullifier = Nullifier::for_dummy(&dummy.nullifier_seed);
let commitment = Commitment::for_dummy(&nullifier, &dummy.commitment_seed);
output.new_nullifiers.push((nullifier, dummy.commitment_root));
output
.new_nullifiers
.push((nullifier, dummy.commitment_root));
output.new_commitments.push(commitment);
output.encrypted_private_post_states.push(dummy.note);
}

View File

@ -2,7 +2,8 @@ use std::collections::{HashMap, VecDeque};
use borsh::{BorshDeserialize, BorshSerialize};
use lee_core::{
DummyInput, InputAccountIdentity, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
DummyInput, InputAccountIdentity, PrivacyPreservingCircuitInput,
PrivacyPreservingCircuitOutput,
account::AccountWithMetadata,
program::{ChainedCall, InstructionData, ProgramId, ProgramOutput},
};

View File

@ -4,10 +4,14 @@ use anyhow::Result;
use keycard_wallet::{KeycardWallet, python_path};
use lee::{AccountId, PrivateKey, PublicKey, Signature};
use lee_core::{
CommitmentSetDigest, DUMMY_COMMITMENT_HASH, Identifier, InputAccountIdentity, MembershipProof,
NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{AccountWithMetadata, Nonce},
encryption::{ViewTag, ViewingPublicKey},
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, DummyInput, EncryptionScheme,
Identifier, InputAccountIdentity, MembershipProof, NullifierPublicKey, NullifierSecretKey,
PrivateAccountKind, SharedSecretKey,
account::{Account, AccountWithMetadata, Nonce},
encryption::{
EncryptedAccountData, EphemeralPublicKey, ML_KEM_768_CIPHERTEXT_LEN, ViewTag,
ViewingPublicKey,
},
};
use rand::{RngCore as _, rngs::OsRng};
@ -410,6 +414,19 @@ impl AccountManager {
.collect()
}
/// Given a count, generate that many dummy inputs with randomized seeds and notes.
/// Uses the given commitment root from the account.
pub fn dummy_inputs(&self, count: usize) -> Vec<DummyInput> {
std::iter::repeat_with(|| DummyInput {
nullifier_seed: random_bytes(),
commitment_seed: random_bytes(),
note: random_dummy_note(),
commitment_root: self.dummy_commitment_root,
})
.take(count)
.collect()
}
/// 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
/// keys (`ssk`) drawn from the cached values that `private_account_keys` and the message
@ -627,6 +644,36 @@ fn random_view_tag() -> ViewTag {
byte[0]
}
fn random_bytes() -> [u8; 32] {
let mut bytes = [0; 32];
OsRng.fill_bytes(&mut bytes);
bytes
}
/// Generates a random note by encoding a default account with random secret key,
/// then generating a random epk value (not connected to the original key) as well
/// as a random tag.
fn random_dummy_note() -> EncryptedAccountData {
let mut secret = [0; 32];
OsRng.fill_bytes(&mut secret);
// The cipher is currently assumed to have default data. The data padding
// leak is a separate issue.
let ciphertext = EncryptionScheme::encrypt(
&Account::default(),
&PrivateAccountKind::Regular(0),
&SharedSecretKey(secret),
&Commitment::new(&AccountId::new([0; 32]), &Account::default()),
0,
);
let mut epk = vec![0; ML_KEM_768_CIPHERTEXT_LEN];
OsRng.fill_bytes(&mut epk);
EncryptedAccountData {
ciphertext,
epk: EphemeralPublicKey(epk),
view_tag: random_view_tag(),
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -569,9 +569,13 @@ impl WalletCore {
instruction_data: InstructionData,
program: &ProgramWithDependencies,
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| {
Ok(())
})
self.send_privacy_preserving_tx_with_pre_check(
accounts,
instruction_data,
program,
|_| Ok(()),
0,
)
.await
}
@ -581,6 +585,7 @@ impl WalletCore {
instruction_data: InstructionData,
program: &ProgramWithDependencies,
tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>,
dummy_count: usize,
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
let acc_manager = account_manager::AccountManager::new(self, accounts).await?;
@ -594,12 +599,14 @@ impl WalletCore {
)?;
let private_account_keys = acc_manager.private_account_keys();
let (output, proof) = lee::privacy_preserving_transaction::circuit::execute_and_prove(
pre_states,
instruction_data,
acc_manager.account_identities(),
&program.to_owned(),
)?;
let (output, proof) =
lee::privacy_preserving_transaction::circuit::execute_and_prove_with_dummy_inputs(
pre_states,
instruction_data,
acc_manager.account_identities(),
acc_manager.dummy_inputs(dummy_count),
&program.to_owned(),
)?;
let message =
lee::privacy_preserving_transaction::message::Message::try_from_circuit_output(

View File

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

View File

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

View File

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