feat(circuit): supply a view_tag on private-account updates

This commit is contained in:
Artem Gureev 2026-07-02 18:30:22 +00:00 committed by agureev
parent 859ffd3900
commit 640d4f6502
23 changed files with 52 additions and 19 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -294,6 +294,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_vpk,
random_seed: [0; 32],
view_tag: 0,
nsk: sender_nsk,
membership_proof: proof,
identifier: 0,

View File

@ -4,7 +4,7 @@ use lee_core::{
PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey,
account::{Account, AccountId, Nonce},
compute_digest_for_path,
encryption::ViewingPublicKey,
encryption::{ViewTag, ViewingPublicKey},
};
use crate::execution_state::ExecutionState;
@ -66,6 +66,7 @@ pub fn compute_circuit_output(
*commitment_root,
);
let new_nonce = Nonce::private_account_nonce_init(&account_id);
let view_tag = EncryptedAccountData::compute_view_tag(&npk, vpk);
emit_private_output(
&mut output,
@ -73,7 +74,7 @@ pub fn compute_circuit_output(
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
&npk,
view_tag,
vpk,
random_seed,
new_nullifier,
@ -83,6 +84,7 @@ pub fn compute_circuit_output(
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk,
random_seed,
view_tag,
nsk,
membership_proof,
identifier,
@ -110,7 +112,7 @@ pub fn compute_circuit_output(
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
&npk,
*view_tag,
vpk,
random_seed,
new_nullifier,
@ -142,6 +144,7 @@ pub fn compute_circuit_output(
*commitment_root,
);
let new_nonce = Nonce::private_account_nonce_init(&account_id);
let view_tag = EncryptedAccountData::compute_view_tag(npk, vpk);
emit_private_output(
&mut output,
@ -149,7 +152,7 @@ pub fn compute_circuit_output(
post_state,
&account_id,
&PrivateAccountKind::Regular(*identifier),
npk,
view_tag,
vpk,
random_seed,
new_nullifier,
@ -190,6 +193,7 @@ pub fn compute_circuit_output(
let (authority_program_id, seed) = pda_seed_by_position
.get(&pos)
.expect("PrivatePdaInit position must be in pda_seed_by_position");
let view_tag = EncryptedAccountData::compute_view_tag(npk, vpk);
emit_private_output(
&mut output,
&mut output_index,
@ -200,7 +204,7 @@ pub fn compute_circuit_output(
seed: *seed,
identifier: *identifier,
},
npk,
view_tag,
vpk,
random_seed,
new_nullifier,
@ -210,6 +214,7 @@ pub fn compute_circuit_output(
InputAccountIdentity::PrivatePdaUpdate {
vpk,
random_seed,
view_tag,
nsk,
membership_proof,
identifier,
@ -234,7 +239,6 @@ pub fn compute_circuit_output(
let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk);
let account_id = pre_state.account_id;
let npk = NullifierPublicKey::from(nsk);
let (authority_program_id, seed) = pda_seed_by_position
.get(&pos)
.expect("PrivatePdaUpdate position must be in pda_seed_by_position");
@ -248,7 +252,7 @@ pub fn compute_circuit_output(
seed: *seed,
identifier: *identifier,
},
&npk,
*view_tag,
vpk,
random_seed,
new_nullifier,
@ -271,7 +275,7 @@ fn emit_private_output(
post_state: Account,
account_id: &AccountId,
kind: &PrivateAccountKind,
npk: &NullifierPublicKey,
view_tag: ViewTag,
vpk: &ViewingPublicKey,
random_seed: &[u8; 32],
new_nullifier: (Nullifier, CommitmentSetDigest),
@ -287,15 +291,6 @@ fn emit_private_output(
let esk = EphemeralSecretKey::new(account_id, random_seed, &new_nonce);
let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(vpk, &esk);
// Currently the view tag is properlty generated for all accounts.
// To increase privacy, this will be changed in the later version
// to only be generated explicitly for initialized accounts and
// fed by the prover directly for updated accounts.
//
// See issue 573:
// https://github.com/logos-blockchain/logos-execution-zone/issues/573
let view_tag = EncryptedAccountData::compute_view_tag(npk, vpk);
let encrypted_account = EncryptionScheme::encrypt(
&post_with_updated_nonce,
kind,

View File

@ -4,7 +4,7 @@ use crate::{
Commitment, CommitmentSetDigest, Identifier, MembershipProof, Nullifier, NullifierPublicKey,
NullifierSecretKey,
account::{Account, AccountWithMetadata},
encryption::{EncryptedAccountData, ViewingPublicKey},
encryption::{EncryptedAccountData, ViewTag, ViewingPublicKey},
program::{BlockValidityWindow, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow},
};
@ -42,6 +42,7 @@ pub enum InputAccountIdentity {
PrivateAuthorizedUpdate {
vpk: ViewingPublicKey,
random_seed: [u8; 32],
view_tag: ViewTag,
nsk: NullifierSecretKey,
membership_proof: MembershipProof,
identifier: Identifier,
@ -79,6 +80,7 @@ pub enum InputAccountIdentity {
PrivatePdaUpdate {
vpk: ViewingPublicKey,
random_seed: [u8; 32],
view_tag: ViewTag,
nsk: NullifierSecretKey,
membership_proof: MembershipProof,
identifier: Identifier,

View File

@ -198,6 +198,7 @@ fn prove_privacy_preserving_execution_circuit_fully_private() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: commitment_set
.get_proof_for(&commitment_sender)
@ -574,6 +575,7 @@ fn private_authorized_update_encrypts_regular_kind_with_identifier() {
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&commitment).unwrap(),
identifier,
@ -630,6 +632,7 @@ fn private_pda_update_encrypts_pda_kind_with_identifier() {
InputAccountIdentity::PrivatePdaUpdate {
vpk: keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
identifier,
@ -708,6 +711,7 @@ fn private_pda_update_identifier_mismatch_fails() {
InputAccountIdentity::PrivatePdaUpdate {
vpk: keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: keys.nsk,
membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(),
identifier: 99,

View File

@ -74,6 +74,7 @@ fn private_changer_claimer_no_data_change_no_claim_succeeds() {
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
@ -104,6 +105,7 @@ fn private_changer_claimer_data_change_no_claim_fails() {
vec![InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,

View File

@ -64,6 +64,7 @@ fn circuit_fails_if_invalid_auth_keys_are_provided() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: recipient_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
@ -113,6 +114,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
@ -162,6 +164,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
@ -211,6 +214,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided(
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
@ -260,6 +264,7 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
@ -307,6 +312,7 @@ fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_bu
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (0, vec![]),
identifier: 0,
@ -694,6 +700,7 @@ fn circuit_should_fail_if_there_are_repeated_ids() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (1, vec![]),
identifier: 0,
@ -701,6 +708,7 @@ fn circuit_should_fail_if_there_are_repeated_ids() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: (1, vec![]),
identifier: 0,
@ -1020,6 +1028,7 @@ fn two_private_pda_family_members_receive_and_spend() {
InputAccountIdentity::PrivatePdaUpdate {
vpk: alice_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_0)
@ -1057,6 +1066,7 @@ fn two_private_pda_family_members_receive_and_spend() {
InputAccountIdentity::PrivatePdaUpdate {
vpk: alice_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_1)
@ -1108,6 +1118,7 @@ fn two_private_pda_family_members_receive_and_spend() {
InputAccountIdentity::PrivatePdaUpdate {
vpk: alice_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: alice_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&commitment_pda_1_after_spend)

View File

@ -328,6 +328,7 @@ fn authorized_public_account_claiming_succeeds_when_executed_privately() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)
@ -443,6 +444,7 @@ fn private_chained_call(number_of_calls: u32) {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: from_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: from_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&from_commitment)
@ -452,6 +454,7 @@ fn private_chained_call(number_of_calls: u32) {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: to_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: to_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&to_commitment)

View File

@ -330,6 +330,7 @@ fn private_balance_transfer_for_tests(
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)
@ -384,6 +385,7 @@ fn deshielded_balance_transfer_for_tests(
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)

View File

@ -524,6 +524,7 @@ fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: recipient_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: recipient_keys.nsk,
membership_proof: state
.get_proof_for_commitment(&recipient_commitment)

View File

@ -167,6 +167,7 @@ fn privacy_malicious_programs_cannot_drain_public_victim() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: attacker_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: attacker_keys.nsk,
membership_proof,
identifier: 0,
@ -327,6 +328,7 @@ fn privacy_malicious_programs_cannot_drain_private_victim() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: attacker_keys.vpk(),
random_seed: [0; 32],
view_tag: 0,
nsk: attacker_keys.nsk,
membership_proof,
identifier: 0,

View File

@ -849,6 +849,7 @@ fn private_bridge_withdraw_invocation_is_dropped() {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: sender_keys.viewing_public_key.clone(),
random_seed: [0; 32],
view_tag: 0,
nsk: sender_keys.private_key_holder.nullifier_secret_key,
membership_proof: state
.get_proof_for_commitment(&sender_commitment)

View File

@ -7,7 +7,7 @@ use lee_core::{
CommitmentSetDigest, DUMMY_COMMITMENT_HASH, Identifier, InputAccountIdentity, MembershipProof,
NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{AccountWithMetadata, Nonce},
encryption::ViewingPublicKey,
encryption::{ViewTag, ViewingPublicKey},
};
use rand::{RngCore as _, rngs::OsRng};
@ -423,6 +423,7 @@ impl AccountManager {
(Some(nsk), Some(membership_proof)) => InputAccountIdentity::PrivatePdaUpdate {
vpk: pre.vpk.clone(),
random_seed: pre.random_seed,
view_tag: random_view_tag(),
nsk,
membership_proof,
identifier: pre.identifier,
@ -442,6 +443,7 @@ impl AccountManager {
InputAccountIdentity::PrivateAuthorizedUpdate {
vpk: pre.vpk.clone(),
random_seed: pre.random_seed,
view_tag: random_view_tag(),
nsk,
membership_proof,
identifier: pre.identifier,
@ -618,6 +620,13 @@ async fn private_shared_acc_preparation(
})
}
/// Generate random byte using OS randomness.
fn random_view_tag() -> ViewTag {
let mut byte: [u8; 1] = [0; 1];
OsRng.fill_bytes(&mut byte);
byte[0]
}
#[cfg(test)]
mod tests {
use super::*;