lssa/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs

182 lines
6.5 KiB
Rust
Raw Normal View History

2025-10-03 18:31:56 -03:00
use std::collections::HashSet;
2025-08-18 07:39:41 -03:00
use risc0_zkvm::{guest::env, serde::to_vec};
use nssa_core::{
2025-09-19 12:23:11 -03:00
Commitment, CommitmentSetDigest, EncryptionScheme, Nullifier, NullifierPublicKey,
PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
account::{Account, AccountId, AccountWithMetadata},
compute_digest_for_path,
encryption::Ciphertext,
program::{DEFAULT_PROGRAM_ID, ProgramOutput, validate_execution},
2025-08-18 07:39:41 -03:00
};
fn main() {
let PrivacyPreservingCircuitInput {
program_output,
visibility_mask,
2025-08-18 18:18:16 -03:00
private_account_nonces,
private_account_keys,
2025-08-18 07:39:41 -03:00
private_account_auth,
program_id,
} = env::read();
2025-08-27 19:25:03 -03:00
// TODO: Check that `program_execution_proof` is one of the allowed built-in programs
// assert_eq!(program_id, AUTHENTICATED_TRANSFER_PROGRAM_ID);
2025-08-18 07:39:41 -03:00
// Check that `program_output` is consistent with the execution of the corresponding program.
env::verify(program_id, &to_vec(&program_output).unwrap()).unwrap();
let ProgramOutput {
pre_states,
post_states,
} = program_output;
2025-10-03 18:31:56 -03:00
// Check that there are no repeated account ids
if !validate_uniqueness_of_account_ids(&pre_states) {
panic!("Repeated account ids found")
}
2025-08-18 07:39:41 -03:00
// Check that the program is well behaved.
// See the # Programs section for the definition of the `validate_execution` method.
2025-09-02 12:38:31 -03:00
if !validate_execution(&pre_states, &post_states, program_id) {
2025-09-02 12:56:01 -03:00
panic!("Bad behaved program");
2025-09-02 12:38:31 -03:00
}
2025-08-18 07:39:41 -03:00
let n_accounts = pre_states.len();
if visibility_mask.len() != n_accounts {
2025-09-02 12:56:01 -03:00
panic!("Invalid visibility mask length");
}
2025-08-18 07:39:41 -03:00
// These lists will be the public outputs of this circuit
// and will be populated next.
let mut public_pre_states: Vec<AccountWithMetadata> = Vec::new();
let mut public_post_states: Vec<Account> = Vec::new();
let mut ciphertexts: Vec<Ciphertext> = Vec::new();
2025-08-18 07:39:41 -03:00
let mut new_commitments: Vec<Commitment> = Vec::new();
2025-08-25 09:22:59 -03:00
let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new();
2025-08-18 07:39:41 -03:00
let mut private_nonces_iter = private_account_nonces.iter();
let mut private_keys_iter = private_account_keys.iter();
let mut private_auth_iter = private_account_auth.iter();
let mut output_index = 0;
2025-08-18 07:39:41 -03:00
for i in 0..n_accounts {
match visibility_mask[i] {
0 => {
2025-08-22 18:49:46 -03:00
// Public account
public_pre_states.push(pre_states[i].clone());
let mut post = post_states[i].clone();
if pre_states[i].is_authorized {
post.nonce += 1;
}
if post.program_owner == DEFAULT_PROGRAM_ID {
// Claim account
post.program_owner = program_id;
}
public_post_states.push(post);
2025-08-18 07:39:41 -03:00
}
1 | 2 => {
let new_nonce = private_nonces_iter.next().expect("Missing private nonce");
2025-08-27 18:02:10 -03:00
let (npk, shared_secret) = private_keys_iter.next().expect("Missing keys");
2025-09-12 09:18:40 -03:00
if AccountId::from(npk) != pre_states[i].account_id {
panic!("AccountId mismatch");
2025-09-10 18:56:34 -03:00
}
if visibility_mask[i] == 1 {
// Private account with authentication
let (nsk, membership_proof) =
private_auth_iter.next().expect("Missing private auth");
2025-08-27 18:23:56 -03:00
// Verify the nullifier public key
2025-08-27 18:02:10 -03:00
let expected_npk = NullifierPublicKey::from(nsk);
if &expected_npk != npk {
panic!("Nullifier public key mismatch");
}
2025-08-25 09:22:59 -03:00
// Compute commitment set digest associated with provided auth path
2025-08-27 18:02:10 -03:00
let commitment_pre = Commitment::new(npk, &pre_states[i].account);
2025-08-26 14:53:02 -03:00
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
// Check pre_state authorization
if !pre_states[i].is_authorized {
panic!("Pre-state not authorized");
}
// Compute nullifier
let nullifier = Nullifier::new(&commitment_pre, nsk);
2025-08-25 09:22:59 -03:00
new_nullifiers.push((nullifier, set_digest));
} else {
2025-08-19 12:52:52 -03:00
if pre_states[i].account != Account::default() {
panic!("Found new private account with non default values.");
}
if pre_states[i].is_authorized {
panic!("Found new private account marked as authorized.");
}
}
// Update post-state with new nonce
let mut post_with_updated_values = post_states[i].clone();
post_with_updated_values.nonce = *new_nonce;
if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID {
2025-08-21 15:52:35 -03:00
// Claim account
post_with_updated_values.program_owner = program_id;
}
2025-08-21 15:52:35 -03:00
// Compute commitment
2025-08-27 18:02:10 -03:00
let commitment_post = Commitment::new(npk, &post_with_updated_values);
// Encrypt and push post state
2025-08-26 14:53:02 -03:00
let encrypted_account = EncryptionScheme::encrypt(
&post_with_updated_values,
shared_secret,
2025-08-26 14:14:08 -03:00
&commitment_post,
output_index,
);
new_commitments.push(commitment_post);
ciphertexts.push(encrypted_account);
output_index += 1;
}
_ => panic!("Invalid visibility mask value"),
2025-08-18 07:39:41 -03:00
}
}
2025-08-19 12:52:52 -03:00
if private_nonces_iter.next().is_some() {
panic!("Too many nonces.");
}
if private_keys_iter.next().is_some() {
2025-09-03 16:44:55 -03:00
panic!("Too many private account keys.");
2025-08-19 12:52:52 -03:00
}
if private_auth_iter.next().is_some() {
panic!("Too many private account authentication keys.");
}
2025-08-18 07:39:41 -03:00
let output = PrivacyPreservingCircuitOutput {
public_pre_states,
public_post_states,
ciphertexts,
2025-08-18 07:39:41 -03:00
new_commitments,
new_nullifiers,
};
env::commit(&output);
}
2025-10-03 18:31:56 -03:00
fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> bool {
let number_of_accounts = pre_states.len();
let number_of_account_ids = pre_states
.iter()
.map(|account| account.account_id.clone())
.collect::<HashSet<_>>()
.len();
number_of_accounts == number_of_account_ids
}