diff --git a/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin b/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin index c4bc8990d..854faeb1c 100644 Binary files a/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin and b/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin differ diff --git a/artifacts/lez/programs/amm.bin b/artifacts/lez/programs/amm.bin index 1300e4b27..e525597dc 100644 Binary files a/artifacts/lez/programs/amm.bin and b/artifacts/lez/programs/amm.bin differ diff --git a/artifacts/lez/programs/associated_token_account.bin b/artifacts/lez/programs/associated_token_account.bin index b6979320c..366ca84ff 100644 Binary files a/artifacts/lez/programs/associated_token_account.bin and b/artifacts/lez/programs/associated_token_account.bin differ diff --git a/artifacts/lez/programs/authenticated_transfer.bin b/artifacts/lez/programs/authenticated_transfer.bin index 6e4c62523..be2c3c0b0 100644 Binary files a/artifacts/lez/programs/authenticated_transfer.bin and b/artifacts/lez/programs/authenticated_transfer.bin differ diff --git a/artifacts/lez/programs/bridge.bin b/artifacts/lez/programs/bridge.bin index 0125c552e..fc34c31c6 100644 Binary files a/artifacts/lez/programs/bridge.bin and b/artifacts/lez/programs/bridge.bin differ diff --git a/artifacts/lez/programs/bridge_lock.bin b/artifacts/lez/programs/bridge_lock.bin index c57ac696a..b4960a537 100644 Binary files a/artifacts/lez/programs/bridge_lock.bin and b/artifacts/lez/programs/bridge_lock.bin differ diff --git a/artifacts/lez/programs/clock.bin b/artifacts/lez/programs/clock.bin index 4a252fa2d..e48379026 100644 Binary files a/artifacts/lez/programs/clock.bin and b/artifacts/lez/programs/clock.bin differ diff --git a/artifacts/lez/programs/cross_zone_inbox.bin b/artifacts/lez/programs/cross_zone_inbox.bin index f277f7daf..f33d9f287 100644 Binary files a/artifacts/lez/programs/cross_zone_inbox.bin and b/artifacts/lez/programs/cross_zone_inbox.bin differ diff --git a/artifacts/lez/programs/cross_zone_outbox.bin b/artifacts/lez/programs/cross_zone_outbox.bin index 1a4e63551..21d807f58 100644 Binary files a/artifacts/lez/programs/cross_zone_outbox.bin and b/artifacts/lez/programs/cross_zone_outbox.bin differ diff --git a/artifacts/lez/programs/faucet.bin b/artifacts/lez/programs/faucet.bin index 7e49e1e8d..0ba466225 100644 Binary files a/artifacts/lez/programs/faucet.bin and b/artifacts/lez/programs/faucet.bin differ diff --git a/artifacts/lez/programs/pinata.bin b/artifacts/lez/programs/pinata.bin index eb74610a0..140965dee 100644 Binary files a/artifacts/lez/programs/pinata.bin and b/artifacts/lez/programs/pinata.bin differ diff --git a/artifacts/lez/programs/pinata_token.bin b/artifacts/lez/programs/pinata_token.bin index 1368a09db..61cc98628 100644 Binary files a/artifacts/lez/programs/pinata_token.bin and b/artifacts/lez/programs/pinata_token.bin differ diff --git a/artifacts/lez/programs/ping_receiver.bin b/artifacts/lez/programs/ping_receiver.bin index becf3f22e..c5d0b3b42 100644 Binary files a/artifacts/lez/programs/ping_receiver.bin and b/artifacts/lez/programs/ping_receiver.bin differ diff --git a/artifacts/lez/programs/ping_sender.bin b/artifacts/lez/programs/ping_sender.bin index 3690be198..572a9e357 100644 Binary files a/artifacts/lez/programs/ping_sender.bin and b/artifacts/lez/programs/ping_sender.bin differ diff --git a/artifacts/lez/programs/sequencer_stake.bin b/artifacts/lez/programs/sequencer_stake.bin index cb8f1ccfb..8d722cbae 100644 Binary files a/artifacts/lez/programs/sequencer_stake.bin and b/artifacts/lez/programs/sequencer_stake.bin differ diff --git a/artifacts/lez/programs/token.bin b/artifacts/lez/programs/token.bin index 5edf727d8..ad9513144 100644 Binary files a/artifacts/lez/programs/token.bin and b/artifacts/lez/programs/token.bin differ diff --git a/artifacts/lez/programs/vault.bin b/artifacts/lez/programs/vault.bin index f7939e88b..aaf4f4f48 100644 Binary files a/artifacts/lez/programs/vault.bin and b/artifacts/lez/programs/vault.bin differ diff --git a/artifacts/lez/programs/wrapped_token.bin b/artifacts/lez/programs/wrapped_token.bin index 304c6da00..574b43bc5 100644 Binary files a/artifacts/lez/programs/wrapped_token.bin and b/artifacts/lez/programs/wrapped_token.bin differ diff --git a/lee/privacy_preserving_circuit/src/execution_state.rs b/lee/privacy_preserving_circuit/src/execution_state.rs index b2157a5da..3a4344749 100644 --- a/lee/privacy_preserving_circuit/src/execution_state.rs +++ b/lee/privacy_preserving_circuit/src/execution_state.rs @@ -55,9 +55,9 @@ pub struct ExecutionState { /// call tree, remaining authorized throughout all calls. globally_authorized: HashSet, signer_account_ids: Vec, - /// This, not `post_states`, is what the circuit ultimately outputs for public accounts: - /// `post_states` is only ever used internally, to give a later call in the same chain a - /// concrete `pre_state` for an account an earlier call already touched. + /// One diff per public account touch; diffs are not aggregated for the same public account. + /// This is the circuit's real output for public accounts and replayed by the sequencer against + /// live state. public_diffs: Vec, } @@ -408,21 +408,12 @@ impl ExecutionState { pre_is_authorized, ); } - // First sighting of a non-PDA-init account (`!has_private_pda_witness`): - // this still runs unconditionally for its caller-PDA-seed-matching side - // effects (a private PDA without an external seed legitimately authorizes - // via the caller's `pda_seeds`), but the signer-list-derived result is only - // *enforced* against `pre.is_authorized` for public accounts, inside - // `authorize_first_sight_without_pda_witness`: `is_authorized` has no - // downstream security consequence for private accounts (claim semantics are - // entirely bypassed for them), and a private `AccountId` can never - // legitimately appear in `signer_account_ids` (it's derived from `npk`/`vpk`, - // not a real-world signature). - // - // Replaces the guarantee live-state reconstruction used to provide: once the - // sequencer stops reconstructing pre-states from live state (PR3), this is - // the circuit's only independent check that `is_authorized` for a top-level - // public account was derived honestly rather than self-reported. + // Enforces that a first-sighted public account's `is_authorized` claim + // matches the real signer set instead of being trusted at face value: now + // that the sequencer no longer reconstructs pre-states from live state, this + // is the circuit's only remaining check that the claim was derived honestly + // rather than self-reported. Also covers a private PDA with no external seed, + // which can still be authorized via the caller's `pda_seeds`. let is_public = account_identities .get(pre_state_position) .is_some_and(InputAccountIdentity::is_public); @@ -476,11 +467,10 @@ impl ExecutionState { // journal we can't derive locally — the resulting `data` — in the same order the host // proved these receipts, matching this function's own traversal. let data = if let Some(diff_data) = diff.diff_data.clone() { - // The diff's materialization logic belongs to the account's *owner* program, not - // necessarily the calling program — falling back to the caller only when the - // account is still unclaimed (default owner). Must match the host's own - // resolution in `circuit::execute_and_prove`, since that's the program whose ELF - // actually produced the receipt being checked below. + // Owned by the account's *owner* program, not necessarily the caller — falls + // back to the caller only when unclaimed (default owner). Must match the host's + // resolution in `circuit::execute_and_prove`, the program whose ELF actually + // produced the receipt checked below. let owner_id: ProgramId = if pre_account.program_owner == DEFAULT_PROGRAM_OWNER { program_id } else { @@ -603,14 +593,10 @@ impl ExecutionState { authorized_accounts } - /// Consume self and yield the validity windows, the per-position PDA seed/program map - /// (recorded during `derive_from_outputs`), the committed signer set, the raw per-call - /// public diffs, and an iterator over pre and (internally materialized) post states of every - /// account involved in the execution. The materialized post state is only authoritative for - /// private accounts — for public ones it was only ever needed internally, for - /// chain-threading; `public_diffs` is the real output for those. Returning everything - /// together keeps the fields module-private rather than forcing them visible to downstream - /// consumers. + /// Consumes self into a tuple so the fields stay module-private rather than exposed + /// individually. The returned post state is only authoritative for private accounts — for + /// public ones it was only ever needed internally, for chain-threading; `public_diffs` is + /// the real output for those. #[expect( clippy::type_complexity, reason = "tuple bundles several exit values from one consuming call so all fields stay private; a struct would only rename it" @@ -765,13 +751,11 @@ fn authorize_first_sight_without_pda_witness( assert_family_binding(pda_family_binding, caller_program_id, seed, pre_account_id); true } else { - // Replaces the guarantee live-state reconstruction used to provide: once the sequencer - // stops reconstructing pre-states from live state, this is the circuit's only - // independent check that `is_authorized` for a top-level public account was derived - // honestly rather than self-reported. `is_authorized` has no downstream security - // consequence for private accounts (claim semantics are entirely bypassed for them), - // and a private `AccountId` can never legitimately appear in `signer_account_ids` (it's - // derived from `npk`/`vpk`, not a real-world signature), so the check is public-only. + // Replaces the check live-state reconstruction used to provide: this is the circuit's + // only remaining proof that a public account's `is_authorized` was derived honestly, not + // self-reported. Private accounts are exempt — `is_authorized` has no security + // consequence for them, and a private `AccountId` (derived from `npk`/`vpk`) can never + // legitimately appear in `signer_account_ids` anyway. assert!( !is_public || pre_is_authorized == is_signer, "is_authorized for account {pre_account_id} doesn't match the canonical signer/PDA-authorization sources", diff --git a/lee/privacy_preserving_circuit/src/output.rs b/lee/privacy_preserving_circuit/src/output.rs index 2ccf3e476..d1db28858 100644 --- a/lee/privacy_preserving_circuit/src/output.rs +++ b/lee/privacy_preserving_circuit/src/output.rs @@ -42,9 +42,8 @@ pub fn compute_circuit_output( { match account_identity { InputAccountIdentity::Public => { - // No longer exported: `pre_state` for public accounts is now witness-only. This - // account's effect is captured entirely by `execution_state`'s `public_diffs` - // (already merged into `output` above via `into_parts`). + // Nothing to add: `pre_state` is witness-only now, and the account's effect is + // already in `output.public_diffs` (merged above via `into_parts`). } InputAccountIdentity::Private(PrivateWitness { vpk, diff --git a/lee/state_machine/core/src/circuit_io.rs b/lee/state_machine/core/src/circuit_io.rs index 584802ea9..77ef816b3 100644 --- a/lee/state_machine/core/src/circuit_io.rs +++ b/lee/state_machine/core/src/circuit_io.rs @@ -33,11 +33,9 @@ pub struct PrivacyPreservingCircuitInput { /// the pre-state), `diff_data` (known from the diff), and this `data`, then checks it via /// `env::verify` — so this value is untrusted input, made trustworthy only by that check. pub update_from_diff_results: Vec, - /// The accounts this transaction claims are signers. `is_authorized` for every account is - /// *derived* from membership in this single list — never accepted as an independent - /// per-account witness — and the list itself is committed to the output so the sequencer can - /// cross-check it against real signatures. Without that, a prover could satisfy - /// claim-eligibility's authorization check for an account it never actually controls. + /// Accounts this transaction claims as signers. Every account's `is_authorized` is derived + /// from membership here, not trusted individually — and the list is committed to the output + /// so the sequencer can cross-check it against real signatures. pub signer_account_ids: Vec, } @@ -173,20 +171,18 @@ pub struct PrivateAction { pub encrypted_post_state: EncryptedAccountData, } -/// One call's raw, unaggregated diff to a public account. +/// One call's raw diff to a public account. /// -/// Deliberately not collapsed into one diff per account: `AccountDiff` has no "combine two -/// diffs" operation, especially for `diff_data`, which only composes by being applied in -/// sequence. The sequencer replays these one at a time against its own live state — never -/// trusting anything the circuit internally materialized for a public account, which is why the -/// account's *value* (as opposed to its diffs) never appears here. +/// Never aggregated per-account, since `AccountDiff` only composes by sequential application +/// (no "combine" operation, especially for `diff_data`). The sequencer replays these one at a +/// time against live state, never trusting what the circuit materialized, which is why only +/// diffs (not values) appear here. #[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))] pub struct PublicDiff { pub account_id: AccountId, - /// Carried alongside the diff because the sequencer's replay-time authorization re-check - /// (and PDA claim resolution) needs to know which program produced it — the same role - /// `chained_call.program_id` plays in the public-transaction path's live materialize loop. + /// Needed for the sequencer's replay-time authorization re-check and PDA claim resolution — + /// the same role `chained_call.program_id` plays in the public-transaction path. pub executing_program_id: ProgramId, pub diff: AccountDiffOutput, } diff --git a/lee/state_machine/src/privacy_preserving_transaction/circuit/mod.rs b/lee/state_machine/src/privacy_preserving_transaction/circuit/mod.rs index ce83fd95a..0d3b074c4 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/circuit/mod.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/circuit/mod.rs @@ -98,13 +98,10 @@ pub fn execute_and_prove_with_padded_inputs( let mut program_outputs = Vec::new(); let mut update_from_diff_results = Vec::new(); - // Captured before `pre_states` moves into `initial_call` below — this is the only place - // `is_authorized` is caller-supplied (every later pre_state's authorization is resolved - // inside the circuit from this same list), so it's also the only place we need to derive - // the committed signer set from. Scoped to public accounts only: this list is committed and - // cross-checked by the sequencer against real, signature-derived `AccountId`s, which a - // private (`npk`/`vpk`-derived) `AccountId` can never match — including a private account - // here would make it permanently unverifiable. + // Captured before `pre_states` moves below — this is the only place `is_authorized` is + // caller-supplied; every later pre_state's authorization is derived inside the circuit from + // this same list. Public accounts only: a private (`npk`/`vpk`-derived) `AccountId` could + // never match a real signature, so including one would make it permanently unverifiable. let signer_account_ids: Vec = pre_states .iter() .zip(&account_identities) @@ -141,9 +138,8 @@ pub fn execute_and_prove_with_padded_inputs( // Prove `update_from_diff` for every account this call's diff writes data to, in the // same order `execution_state::derive_from_outputs` will visit them, so // `update_from_diff_results` lines up positionally with the circuit's own traversal. - // The diff's materialization logic belongs to the account's *owner* program, not - // necessarily the calling program — falling back to the caller only when the account - // is still unclaimed (default owner), mirroring the claim-eligibility rule elsewhere. + // Dispatched to the account's *owner* program, not necessarily the caller — falls back + // to the caller only when the account is still unclaimed (default owner). for (pre, diff_output) in program_output .pre_states .iter() diff --git a/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs b/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs index 3979dd439..9a630cab6 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs @@ -101,10 +101,8 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() assert!(proof.is_valid_for(&output)); - // The sender's `AccountDiff`, not a materialized post-state — this is the whole point of - // `AccountDiff`: the circuit never commits to a specific public post-state, only to what - // changed, so the sequencer can replay it against whatever the account's live state is by - // the time it processes this transaction. + // The sender's diff, not a materialized post-state — the whole point of `PublicDiff`: commit + // only to what changed, so the sequencer can replay it against live state. let [public_diff] = output.public_diffs.try_into().unwrap(); assert_eq!(public_diff.account_id, expected_sender_pre.account_id); assert_eq!(public_diff.executing_program_id, program.id()); diff --git a/lee/state_machine/src/privacy_preserving_transaction/message.rs b/lee/state_machine/src/privacy_preserving_transaction/message.rs index 28e1b05df..a29ba4aa0 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/message.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/message.rs @@ -16,8 +16,7 @@ const PREFIX: &[u8; 32] = b"/LEE/v0.3/Message/Privacy/\x00\x00\x00\x00\x00\x00"; #[derive(Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Message { - /// Raw, per-call, unaggregated diffs for public accounts. See - /// `PrivacyPreservingCircuitOutput::public_diffs`. + /// Per-call, unaggregated diffs for public accounts, copied from the circuit's output. pub public_diffs: Vec, pub nonces: Vec, pub private_actions: Vec, @@ -88,10 +87,8 @@ impl Message { .collect() } - /// The unique set of public accounts this transaction touches. `public_diffs` can - /// legitimately repeat an account (multiple calls touching the same account within one - /// transaction), so this dedups — callers rely on "each affected account listed once" (see - /// `PrivacyPreservingTransaction::affected_public_account_ids`). + /// The unique set of public accounts this transaction touches. `public_diffs` can repeat an + /// account across calls, so this dedups — callers depend on each account being listed once. #[must_use] pub fn public_account_ids(&self) -> Vec { let mut seen = HashSet::new(); diff --git a/lee/state_machine/src/state/tests/circuit.rs b/lee/state_machine/src/state/tests/circuit.rs index 0144cb204..655ccb8e4 100644 --- a/lee/state_machine/src/state/tests/circuit.rs +++ b/lee/state_machine/src/state/tests/circuit.rs @@ -865,10 +865,9 @@ fn delegated_public_pda_first_seen_in_callee_is_authorized() { ) .expect("a caller's pda_seeds must authorize a public PDA it delegates at first sight"); - // The callee ran with the PDA authorized (auth_asserting_noop did not panic). The - // pre-state's is_authorized view is no longer exported to the journal at all (removed - // along with public_pre_states — nothing left to audit once it isn't part of the proven - // output), so only the touched account's identity is left to check here. + // The callee ran with the PDA authorized (auth_asserting_noop didn't panic). `is_authorized` + // is no longer exported to the journal at all, so only the touched account's identity is + // left to check here. assert_eq!(output.public_diffs.len(), 1); assert_eq!(output.public_diffs[0].account_id, pda); } @@ -908,11 +907,8 @@ fn public_pda_first_sight_grant_does_not_extend_to_sibling_calls() { fn public_account_first_sight_authorization_without_signer_backing_is_rejected() { let seed = PdaSeed::new([77; 32]); - // Regression pin for the forgery PR3's signer check closes: `undeclaring_pda_delegator` - // self-declares `is_authorized = true` on a plain public account (not the seed's derived - // PDA, so no caller-seed match either) with no backing signer. Once `public_pre_states` - // stopped being exported, the circuit's signer-set check became the only thing standing - // between a self-reported claim and the proof succeeding — it must reject this. + // Regression pin: `undeclaring_pda_delegator` self-declares `is_authorized = true` with no + // caller-seed match and no backing signer — the signer-set check must reject this claim. let result = undeclaring_public_delegation( AccountId::new([9; 32]), Some(seed), @@ -931,9 +927,8 @@ fn wrong_seed_public_pda_first_sight_without_signer_backing_is_rejected() { let delegator_id = crate::test_methods::undeclaring_pda_delegator().id(); let pda = AccountId::for_public_pda(&delegator_id, &seed); - // An unmatched seed falls back to the credential-claim path, which is subject to the same - // signer-backing requirement as any other first-sighted public account — regression pin, - // see `public_account_first_sight_authorization_without_signer_backing_is_rejected`. + // An unmatched seed falls back to the credential-claim path, which needs the same + // signer backing as any first-sighted public account. let result = undeclaring_public_delegation( pda, Some(wrong_seed), diff --git a/lee/state_machine/src/state/tests/privacy_preserving.rs b/lee/state_machine/src/state/tests/privacy_preserving.rs index f47d99fd7..fea64aa7a 100644 --- a/lee/state_machine/src/state/tests/privacy_preserving.rs +++ b/lee/state_machine/src/state/tests/privacy_preserving.rs @@ -306,11 +306,9 @@ fn data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_ &program.into(), ); - // The account's owner ([0,1,2,3,4,5,6,7]) is not `data_changer` itself and isn't declared - // as a dependency, so the host can't resolve whose `update_from_diff` logic should interpret - // this diff — caught here, before the outer circuit is even proven, rather than surfacing as - // a `validate_execution` panic inside the circuit (`CircuitProvingError`) the way it would if - // the account were instead owned by some other *declared* program. + // The account's owner isn't `data_changer` and isn't a declared dependency, so the host + // can't resolve whose `update_from_diff` should run — caught here, before proving, rather + // than as a `validate_execution` panic inside the circuit. assert!(matches!( result, Err(LeeError::InvalidProgramBehavior( diff --git a/lee/state_machine/src/validated_state_diff/mod.rs b/lee/state_machine/src/validated_state_diff/mod.rs index 9655dd03f..438b870db 100644 --- a/lee/state_machine/src/validated_state_diff/mod.rs +++ b/lee/state_machine/src/validated_state_diff/mod.rs @@ -413,10 +413,9 @@ impl ValidatedStateDiff { LeeError::OutOfValidityWindow ); - // Anchor the circuit's claimed signer set against real, cryptographically-verified - // signatures. Without this, a prover could satisfy claim-eligibility's authorization - // check for an account it never actually controls, since `is_authorized` inside the - // circuit is derived entirely from this list. + // Cross-checks the circuit's claimed signers against real signatures. `is_authorized` + // inside the circuit is derived entirely from this list, so without this check a prover + // could claim control of an account it doesn't own. ensure!( message .signer_account_ids @@ -436,12 +435,9 @@ impl ValidatedStateDiff { // 5. Nullifier uniqueness state.check_nullifiers_are_valid(&nullifiers)?; - // Replay each public diff against live state, one at a time — never trusting anything - // the circuit internally materialized for a public account. This, not proof - // verification above, is what actually avoids tying this transaction's validity to a - // stale public-account snapshot: this step only cares about the diff's shape and this - // program's ownership, both independent of whatever pre-state the circuit witnessed - // while proving. + // Replay each public diff against live state, one at a time, never trusting what the + // circuit materialized — this is what actually decouples validity from a stale + // snapshot, checking only the diff's shape and ownership, not the witnessed pre-state. let mut public_diff: HashMap = HashMap::new(); for public_diff_entry in &message.public_diffs { let account_id = public_diff_entry.account_id; @@ -453,12 +449,11 @@ impl ValidatedStateDiff { let diff = public_diff_entry.diff.diff(); let account_program_owner = pre_account.program_owner; // `program_owner` is `AccountId`-typed; convert once up front rather than at each - // comparison below (see `From for AccountId`'s doc comment). + // comparison below. let executing_account_id = AccountId::from(executing_program_id); - // Re-check authorization against *live* state — the circuit's own - // `validate_execution` check ran against a witnessed pre-state that isn't trusted - // for this purpose (see `check_privacy_preserving_circuit_proof_is_valid`). + // Re-check authorization against *live* state: the circuit's own `validate_execution` + // ran against a witnessed pre-state, which isn't trusted for this purpose. ensure!( !matches!(diff.diff_balance, BalanceDiff::Sub(amount) if amount > 0) || account_program_owner == executing_account_id, @@ -612,10 +607,9 @@ fn authenticate_public_transaction_signers( Ok(signer_account_ids) } -/// Verifies the proof against exactly what the circuit witnessed and output. Public account -/// pre-states are a circuit-internal secret witness never committed to the journal at all, so -/// there is nothing here to reconcile against live state in the first place. Materialization -/// (which *does* use live state) happens separately, later, via `message.public_diffs`. +/// Verifies the proof against exactly what the circuit witnessed and output. Public pre-states +/// are a circuit-internal secret witness, never committed to the journal — materialization +/// (which *does* use live state) happens later, via `message.public_diffs`. fn check_privacy_preserving_circuit_proof_is_valid( proof: &Proof, message: &Message, diff --git a/lee/state_machine/src/validated_state_diff/tests.rs b/lee/state_machine/src/validated_state_diff/tests.rs index bdb84f8b4..a1c82e148 100644 --- a/lee/state_machine/src/validated_state_diff/tests.rs +++ b/lee/state_machine/src/validated_state_diff/tests.rs @@ -469,13 +469,11 @@ fn privacy_garbage_proof_is_rejected() { } } -/// The race condition this whole `AccountDiff` design exists to fix: a public account touched -/// by a privacy transaction changes on-chain *after* the proof was generated but *before* the -/// sequencer validates it (e.g. an unrelated public transfer into/out of the same account -/// landing first). Proof validity no longer depends on a specific public-account snapshot — only -/// on what the circuit itself witnessed and output — so the proof still verifies, and the -/// diff it carries gets replayed against whatever the live balance actually is by the time the -/// sequencer processes it, not the stale balance captured at proving time. +/// The race condition this whole `AccountDiff` design fixes: a public account changes on-chain +/// after proving but before sequencer validation (e.g. an unrelated transfer lands first). Proof +/// validity no longer depends on a snapshot — only on what the circuit witnessed — so the proof +/// still verifies, and its diff replays against the live balance at validation time, not the +/// stale one captured while proving. #[test] fn privacy_transaction_survives_public_state_changing_after_proving() { use lee_core::{ diff --git a/test_fixtures/fixtures/prebuilt_sequencer_db.dump b/test_fixtures/fixtures/prebuilt_sequencer_db.dump index 180269de8..20747cc5c 100644 Binary files a/test_fixtures/fixtures/prebuilt_sequencer_db.dump and b/test_fixtures/fixtures/prebuilt_sequencer_db.dump differ