mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-02 13:23:10 +00:00
Merge pull request #151 from logos-blockchain/schouhy/implement-privacy-preserving-tail-calls
Implement privacy preserving tail calls
This commit is contained in:
commit
abb1fc5e45
Binary file not shown.
@ -168,7 +168,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
(recipient_npk.clone(), recipient_ss),
|
||||
],
|
||||
&[(sender_nsk, proof)],
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let message = pptx::message::Message::try_from_circuit_output(
|
||||
|
||||
@ -25,8 +25,8 @@ pub struct Account {
|
||||
pub nonce: Nonce,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug))]
|
||||
pub struct AccountWithMetadata {
|
||||
pub account: Account,
|
||||
pub is_authorized: bool,
|
||||
|
||||
@ -10,7 +10,7 @@ use crate::{
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PrivacyPreservingCircuitInput {
|
||||
pub program_output: ProgramOutput,
|
||||
pub program_outputs: Vec<ProgramOutput>,
|
||||
pub visibility_mask: Vec<u8>,
|
||||
pub private_account_nonces: Vec<Nonce>,
|
||||
pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -8,6 +10,7 @@ use crate::account::{Account, AccountWithMetadata};
|
||||
pub type ProgramId = [u32; 8];
|
||||
pub type InstructionData = Vec<u32>;
|
||||
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
|
||||
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
|
||||
|
||||
pub struct ProgramInput<T> {
|
||||
pub pre_states: Vec<AccountWithMetadata>,
|
||||
@ -54,7 +57,9 @@ impl From<(&ProgramId, &PdaSeed)> for AccountId {
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
pub struct ChainedCall {
|
||||
/// The program ID of the program to execute
|
||||
pub program_id: ProgramId,
|
||||
/// The instruction data to pass
|
||||
pub instruction_data: InstructionData,
|
||||
pub pre_states: Vec<AccountWithMetadata>,
|
||||
pub pda_seeds: Vec<PdaSeed>,
|
||||
@ -111,26 +116,34 @@ impl AccountPostState {
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))]
|
||||
pub struct ProgramOutput {
|
||||
/// The instruction data the program received to produce this output
|
||||
pub instruction_data: InstructionData,
|
||||
/// The account pre states the program received to produce this output
|
||||
pub pre_states: Vec<AccountWithMetadata>,
|
||||
pub post_states: Vec<AccountPostState>,
|
||||
pub chained_calls: Vec<ChainedCall>,
|
||||
}
|
||||
|
||||
pub fn read_nssa_inputs<T: DeserializeOwned>() -> ProgramInput<T> {
|
||||
pub fn read_nssa_inputs<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionData) {
|
||||
let pre_states: Vec<AccountWithMetadata> = env::read();
|
||||
let instruction_words: InstructionData = env::read();
|
||||
let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap();
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction,
|
||||
}
|
||||
(
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction,
|
||||
},
|
||||
instruction_words,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_nssa_outputs(
|
||||
instruction_data: InstructionData,
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
post_states: Vec<AccountPostState>,
|
||||
) {
|
||||
let output = ProgramOutput {
|
||||
instruction_data,
|
||||
pre_states,
|
||||
post_states,
|
||||
chained_calls: Vec::new(),
|
||||
@ -139,11 +152,13 @@ pub fn write_nssa_outputs(
|
||||
}
|
||||
|
||||
pub fn write_nssa_outputs_with_chained_call(
|
||||
instruction_data: InstructionData,
|
||||
pre_states: Vec<AccountWithMetadata>,
|
||||
post_states: Vec<AccountPostState>,
|
||||
chained_calls: Vec<ChainedCall>,
|
||||
) {
|
||||
let output = ProgramOutput {
|
||||
instruction_data,
|
||||
pre_states,
|
||||
post_states,
|
||||
chained_calls,
|
||||
@ -162,32 +177,37 @@ pub fn validate_execution(
|
||||
post_states: &[AccountPostState],
|
||||
executing_program_id: ProgramId,
|
||||
) -> bool {
|
||||
// 1. Lengths must match
|
||||
// 1. Check account ids are all different
|
||||
if !validate_uniqueness_of_account_ids(pre_states) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Lengths must match
|
||||
if pre_states.len() != post_states.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (pre, post) in pre_states.iter().zip(post_states) {
|
||||
// 2. Nonce must remain unchanged
|
||||
// 3. Nonce must remain unchanged
|
||||
if pre.account.nonce != post.account.nonce {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Program ownership changes are not allowed
|
||||
// 4. Program ownership changes are not allowed
|
||||
if pre.account.program_owner != post.account.program_owner {
|
||||
return false;
|
||||
}
|
||||
|
||||
let account_program_owner = pre.account.program_owner;
|
||||
|
||||
// 4. Decreasing balance only allowed if owned by executing program
|
||||
// 5. Decreasing balance only allowed if owned by executing program
|
||||
if post.account.balance < pre.account.balance
|
||||
&& account_program_owner != executing_program_id
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. Data changes only allowed if owned by executing program or if account pre state has
|
||||
// 6. Data changes only allowed if owned by executing program or if account pre state has
|
||||
// default values
|
||||
if pre.account.data != post.account.data
|
||||
&& pre.account != Account::default()
|
||||
@ -196,14 +216,14 @@ pub fn validate_execution(
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. If a post state has default program owner, the pre state must have been a default
|
||||
// 7. If a post state has default program owner, the pre state must have been a default
|
||||
// account
|
||||
if post.account.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Total balance is preserved
|
||||
// 8. Total balance is preserved
|
||||
|
||||
let Some(total_balance_pre_states) =
|
||||
WrappedBalanceSum::from_balances(pre_states.iter().map(|pre| pre.account.balance))
|
||||
@ -224,6 +244,17 @@ pub fn validate_execution(
|
||||
true
|
||||
}
|
||||
|
||||
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)
|
||||
.collect::<HashSet<_>>()
|
||||
.len();
|
||||
|
||||
number_of_accounts == number_of_account_ids
|
||||
}
|
||||
|
||||
/// Representation of a number as `lo + hi * 2^128`.
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct WrappedBalanceSum {
|
||||
|
||||
@ -6,34 +6,37 @@ use nssa_core::{
|
||||
};
|
||||
|
||||
/// Initializes a default account under the ownership of this program.
|
||||
fn initialize_account(pre_state: AccountWithMetadata) {
|
||||
fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState {
|
||||
let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone());
|
||||
let is_authorized = pre_state.is_authorized;
|
||||
|
||||
// Continue only if the account to claim has default values
|
||||
if account_to_claim.account() != &Account::default() {
|
||||
return;
|
||||
panic!("Account must be uninitialized");
|
||||
}
|
||||
|
||||
// Continue only if the owner authorized this operation
|
||||
if !is_authorized {
|
||||
return;
|
||||
panic!("Invalid input");
|
||||
}
|
||||
|
||||
// Noop will result in account being claimed for this program
|
||||
write_nssa_outputs(vec![pre_state], vec![account_to_claim]);
|
||||
account_to_claim
|
||||
}
|
||||
|
||||
/// Transfers `balance_to_move` native balance from `sender` to `recipient`.
|
||||
fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) {
|
||||
fn transfer(
|
||||
sender: AccountWithMetadata,
|
||||
recipient: AccountWithMetadata,
|
||||
balance_to_move: u128,
|
||||
) -> Vec<AccountPostState> {
|
||||
// Continue only if the sender has authorized this operation
|
||||
if !sender.is_authorized {
|
||||
return;
|
||||
panic!("Invalid input");
|
||||
}
|
||||
|
||||
// Continue only if the sender has enough balance
|
||||
if sender.account.balance < balance_to_move {
|
||||
return;
|
||||
panic!("Invalid input");
|
||||
}
|
||||
|
||||
// Create accounts post states, with updated balances
|
||||
@ -57,23 +60,31 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance
|
||||
}
|
||||
};
|
||||
|
||||
write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]);
|
||||
vec![sender_post, recipient_post]
|
||||
}
|
||||
|
||||
/// A transfer of balance program.
|
||||
/// To be used both in public and private contexts.
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance_to_move,
|
||||
} = read_nssa_inputs();
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance_to_move,
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs();
|
||||
|
||||
match (pre_states.as_slice(), balance_to_move) {
|
||||
([account_to_claim], 0) => initialize_account(account_to_claim.clone()),
|
||||
let post_states = match (pre_states.as_slice(), balance_to_move) {
|
||||
([account_to_claim], 0) => {
|
||||
let post = initialize_account(account_to_claim.clone());
|
||||
vec![post]
|
||||
}
|
||||
([sender, recipient], balance_to_move) => {
|
||||
transfer(sender.clone(), recipient.clone(), balance_to_move)
|
||||
}
|
||||
_ => panic!("invalid params"),
|
||||
}
|
||||
};
|
||||
|
||||
write_nssa_outputs(instruction_words, pre_states, post_states);
|
||||
}
|
||||
|
||||
@ -44,10 +44,13 @@ impl Challenge {
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
// It is expected to receive only two accounts: [pinata_account, winner_account]
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: solution,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: solution,
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pinata, winner] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -71,6 +74,7 @@ fn main() {
|
||||
winner_post.balance += PRIZE;
|
||||
|
||||
write_nssa_outputs(
|
||||
instruction_words,
|
||||
vec![pinata, winner],
|
||||
vec![
|
||||
AccountPostState::new(pinata_post),
|
||||
|
||||
@ -54,10 +54,13 @@ fn main() {
|
||||
// Read input accounts.
|
||||
// It is expected to receive three accounts: [pinata_definition, pinata_token_holding,
|
||||
// winner_token_holding]
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: solution,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: solution,
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [
|
||||
pinata_definition,
|
||||
@ -98,6 +101,7 @@ fn main() {
|
||||
}];
|
||||
|
||||
write_nssa_outputs_with_chained_call(
|
||||
instruction_words,
|
||||
vec![
|
||||
pinata_definition,
|
||||
pinata_token_holding,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nssa_core::{
|
||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier,
|
||||
@ -6,43 +6,114 @@ use nssa_core::{
|
||||
account::{Account, AccountId, AccountWithMetadata},
|
||||
compute_digest_for_path,
|
||||
encryption::Ciphertext,
|
||||
program::{DEFAULT_PROGRAM_ID, ProgramOutput, validate_execution},
|
||||
program::{DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, validate_execution},
|
||||
};
|
||||
use risc0_zkvm::{guest::env, serde::to_vec};
|
||||
|
||||
fn main() {
|
||||
let PrivacyPreservingCircuitInput {
|
||||
program_output,
|
||||
program_outputs,
|
||||
visibility_mask,
|
||||
private_account_nonces,
|
||||
private_account_keys,
|
||||
private_account_auth,
|
||||
program_id,
|
||||
mut program_id,
|
||||
} = env::read();
|
||||
|
||||
// Check that `program_output` is consistent with the execution of the corresponding program.
|
||||
env::verify(program_id, &to_vec(&program_output).unwrap()).unwrap();
|
||||
let mut pre_states: Vec<AccountWithMetadata> = Vec::new();
|
||||
let mut state_diff: HashMap<AccountId, Account> = HashMap::new();
|
||||
|
||||
let ProgramOutput {
|
||||
pre_states,
|
||||
post_states,
|
||||
chained_calls,
|
||||
} = program_output;
|
||||
|
||||
// TODO: implement chained calls for privacy preserving transactions
|
||||
if !chained_calls.is_empty() {
|
||||
panic!("Privacy preserving transactions do not support yet chained calls.")
|
||||
let num_calls = program_outputs.len();
|
||||
if num_calls > MAX_NUMBER_CHAINED_CALLS {
|
||||
panic!("Max chained calls depth is exceeded");
|
||||
}
|
||||
|
||||
// Check that there are no repeated account ids
|
||||
if !validate_uniqueness_of_account_ids(&pre_states) {
|
||||
panic!("Repeated account ids found")
|
||||
let Some(last_program_call) = program_outputs.last() else {
|
||||
panic!("Program outputs is empty")
|
||||
};
|
||||
|
||||
if !last_program_call.chained_calls.is_empty() {
|
||||
panic!("Call stack is incomplete");
|
||||
}
|
||||
|
||||
// Check that the program is well behaved.
|
||||
// See the # Programs section for the definition of the `validate_execution` method.
|
||||
if !validate_execution(&pre_states, &post_states, program_id) {
|
||||
panic!("Bad behaved program");
|
||||
for window in program_outputs.windows(2) {
|
||||
let caller = &window[0];
|
||||
let callee = &window[1];
|
||||
|
||||
if caller.chained_calls.len() > 1 {
|
||||
panic!("Privacy Multi-chained calls are not supported yet");
|
||||
}
|
||||
|
||||
// TODO: Modify when multi-chain calls are supported in the circuit
|
||||
let Some(caller_chained_call) = &caller.chained_calls.first() else {
|
||||
panic!("Expected chained call");
|
||||
};
|
||||
|
||||
// Check that instruction data in caller is the instruction data in callee
|
||||
if caller_chained_call.instruction_data != callee.instruction_data {
|
||||
panic!("Invalid instruction data");
|
||||
}
|
||||
|
||||
// Check that account pre_states in caller are the ones in calle
|
||||
if caller_chained_call.pre_states != callee.pre_states {
|
||||
panic!("Invalid pre states");
|
||||
}
|
||||
}
|
||||
|
||||
for (i, program_output) in program_outputs.iter().enumerate() {
|
||||
let mut program_output = program_output.clone();
|
||||
|
||||
// Check that `program_output` is consistent with the execution of the corresponding program.
|
||||
let program_output_words =
|
||||
&to_vec(&program_output).expect("program_output must be serializable");
|
||||
env::verify(program_id, program_output_words)
|
||||
.expect("program output must match the program's execution");
|
||||
|
||||
// Check that the program is well behaved.
|
||||
// See the # Programs section for the definition of the `validate_execution` method.
|
||||
if !validate_execution(
|
||||
&program_output.pre_states,
|
||||
&program_output.post_states,
|
||||
program_id,
|
||||
) {
|
||||
panic!("Bad behaved program");
|
||||
}
|
||||
|
||||
// The invoked program claims the accounts with default program id.
|
||||
for post in program_output
|
||||
.post_states
|
||||
.iter_mut()
|
||||
.filter(|post| post.requires_claim())
|
||||
{
|
||||
// The invoked program can only claim accounts with default program id.
|
||||
if post.account().program_owner == DEFAULT_PROGRAM_ID {
|
||||
post.account_mut().program_owner = program_id;
|
||||
} else {
|
||||
panic!("Cannot claim an initialized account")
|
||||
}
|
||||
}
|
||||
|
||||
for (pre, post) in program_output
|
||||
.pre_states
|
||||
.iter()
|
||||
.zip(&program_output.post_states)
|
||||
{
|
||||
if let Some(account_pre) = state_diff.get(&pre.account_id) {
|
||||
if account_pre != &pre.account {
|
||||
panic!("Invalid input");
|
||||
}
|
||||
} else {
|
||||
pre_states.push(pre.clone());
|
||||
}
|
||||
state_diff.insert(pre.account_id.clone(), post.account().clone());
|
||||
}
|
||||
|
||||
// TODO: Modify when multi-chain calls are supported in the circuit
|
||||
if let Some(next_chained_call) = &program_output.chained_calls.first() {
|
||||
program_id = next_chained_call.program_id;
|
||||
} else if i != program_outputs.len() - 1 {
|
||||
panic!("Inner call without a chained call found")
|
||||
};
|
||||
}
|
||||
|
||||
let n_accounts = pre_states.len();
|
||||
@ -69,7 +140,7 @@ fn main() {
|
||||
// Public account
|
||||
public_pre_states.push(pre_states[i].clone());
|
||||
|
||||
let mut post = post_states[i].account().clone();
|
||||
let mut post = state_diff.get(&pre_states[i].account_id).unwrap().clone();
|
||||
if pre_states[i].is_authorized {
|
||||
post.nonce += 1;
|
||||
}
|
||||
@ -125,7 +196,8 @@ fn main() {
|
||||
}
|
||||
|
||||
// Update post-state with new nonce
|
||||
let mut post_with_updated_values = post_states[i].account().clone();
|
||||
let mut post_with_updated_values =
|
||||
state_diff.get(&pre_states[i].account_id).unwrap().clone();
|
||||
post_with_updated_values.nonce = *new_nonce;
|
||||
|
||||
if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID {
|
||||
@ -174,14 +246,3 @@ fn main() {
|
||||
|
||||
env::commit(&output);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -249,10 +249,13 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec<AccountPostStat
|
||||
type Instruction = [u8; 23];
|
||||
|
||||
fn main() {
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction,
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let post_states = match instruction[0] {
|
||||
0 => {
|
||||
@ -295,7 +298,7 @@ fn main() {
|
||||
_ => panic!("Invalid instruction"),
|
||||
};
|
||||
|
||||
write_nssa_outputs(pre_states, post_states);
|
||||
write_nssa_outputs(instruction_words, pre_states, post_states);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput,
|
||||
PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
account::AccountWithMetadata,
|
||||
program::{InstructionData, ProgramOutput},
|
||||
program::{InstructionData, ProgramId, ProgramOutput},
|
||||
};
|
||||
use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover};
|
||||
|
||||
@ -11,12 +13,35 @@ use crate::{
|
||||
error::NssaError,
|
||||
program::Program,
|
||||
program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID},
|
||||
state::MAX_NUMBER_CHAINED_CALLS,
|
||||
};
|
||||
|
||||
/// Proof of the privacy preserving execution circuit
|
||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
pub struct Proof(pub(crate) Vec<u8>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ProgramWithDependencies {
|
||||
pub program: Program,
|
||||
// TODO: avoid having a copy of the bytecode of each dependency.
|
||||
pub dependencies: HashMap<ProgramId, Program>,
|
||||
}
|
||||
|
||||
impl ProgramWithDependencies {
|
||||
pub fn new(program: Program, dependencies: HashMap<ProgramId, Program>) -> Self {
|
||||
Self {
|
||||
program,
|
||||
dependencies,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Program> for ProgramWithDependencies {
|
||||
fn from(program: Program) -> Self {
|
||||
ProgramWithDependencies::new(program, HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a proof of the execution of a NSSA program inside the privacy preserving execution
|
||||
/// circuit
|
||||
pub fn execute_and_prove(
|
||||
@ -26,27 +51,64 @@ pub fn execute_and_prove(
|
||||
private_account_nonces: &[u128],
|
||||
private_account_keys: &[(NullifierPublicKey, SharedSecretKey)],
|
||||
private_account_auth: &[(NullifierSecretKey, MembershipProof)],
|
||||
program: &Program,
|
||||
program_with_dependencies: &ProgramWithDependencies,
|
||||
) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> {
|
||||
let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?;
|
||||
let mut program = &program_with_dependencies.program;
|
||||
let dependencies = &program_with_dependencies.dependencies;
|
||||
let mut instruction_data = instruction_data.clone();
|
||||
let mut pre_states = pre_states.to_vec();
|
||||
let mut env_builder = ExecutorEnv::builder();
|
||||
let mut program_outputs = Vec::new();
|
||||
|
||||
let program_output: ProgramOutput = inner_receipt
|
||||
.journal
|
||||
.decode()
|
||||
.map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?;
|
||||
for _i in 0..MAX_NUMBER_CHAINED_CALLS {
|
||||
let inner_receipt = execute_and_prove_program(program, &pre_states, &instruction_data)?;
|
||||
|
||||
let program_output: ProgramOutput = inner_receipt
|
||||
.journal
|
||||
.decode()
|
||||
.map_err(|e| NssaError::ProgramOutputDeserializationError(e.to_string()))?;
|
||||
|
||||
// TODO: remove clone
|
||||
program_outputs.push(program_output.clone());
|
||||
|
||||
// Prove circuit.
|
||||
env_builder.add_assumption(inner_receipt);
|
||||
|
||||
// TODO: Remove when multi-chain calls are supported in the circuit
|
||||
assert!(program_output.chained_calls.len() <= 1);
|
||||
// TODO: Modify when multi-chain calls are supported in the circuit
|
||||
if let Some(next_call) = program_output.chained_calls.first() {
|
||||
program = dependencies
|
||||
.get(&next_call.program_id)
|
||||
.ok_or(NssaError::InvalidProgramBehavior)?;
|
||||
instruction_data = next_call.instruction_data.clone();
|
||||
// Build post states with metadata for next call
|
||||
let mut post_states_with_metadata = Vec::new();
|
||||
for (pre, post) in program_output
|
||||
.pre_states
|
||||
.iter()
|
||||
.zip(program_output.post_states)
|
||||
{
|
||||
let mut post_with_metadata = pre.clone();
|
||||
post_with_metadata.account = post.account().clone();
|
||||
post_states_with_metadata.push(post_with_metadata);
|
||||
}
|
||||
|
||||
pre_states = next_call.pre_states.clone();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let circuit_input = PrivacyPreservingCircuitInput {
|
||||
program_output,
|
||||
program_outputs,
|
||||
visibility_mask: visibility_mask.to_vec(),
|
||||
private_account_nonces: private_account_nonces.to_vec(),
|
||||
private_account_keys: private_account_keys.to_vec(),
|
||||
private_account_auth: private_account_auth.to_vec(),
|
||||
program_id: program.id(),
|
||||
program_id: program_with_dependencies.program.id(),
|
||||
};
|
||||
|
||||
// Prove circuit.
|
||||
let mut env_builder = ExecutorEnv::builder();
|
||||
env_builder.add_assumption(inner_receipt);
|
||||
env_builder.write(&circuit_input).unwrap();
|
||||
let env = env_builder.build().unwrap();
|
||||
let prover = default_prover();
|
||||
@ -156,7 +218,7 @@ mod tests {
|
||||
&[0xdeadbeef],
|
||||
&[(recipient_keys.npk(), shared_secret.clone())],
|
||||
&[],
|
||||
&Program::authenticated_transfer_program(),
|
||||
&Program::authenticated_transfer_program().into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -257,7 +319,7 @@ mod tests {
|
||||
sender_keys.nsk,
|
||||
commitment_set.get_proof_for(&commitment_sender).unwrap(),
|
||||
)],
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ use crate::{
|
||||
/// TODO: Make this variable when fees are implemented
|
||||
const MAX_NUM_CYCLES_PUBLIC_EXECUTION: u64 = 1024 * 1024 * 32; // 32M cycles
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Program {
|
||||
id: ProgramId,
|
||||
elf: Vec<u8>,
|
||||
|
||||
@ -272,7 +272,10 @@ pub mod tests {
|
||||
error::NssaError,
|
||||
execute_and_prove,
|
||||
privacy_preserving_transaction::{
|
||||
PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet,
|
||||
PrivacyPreservingTransaction,
|
||||
circuit::{self, ProgramWithDependencies},
|
||||
message::Message,
|
||||
witness_set::WitnessSet,
|
||||
},
|
||||
program::Program,
|
||||
public_transaction,
|
||||
@ -859,7 +862,7 @@ pub mod tests {
|
||||
&[0xdeadbeef],
|
||||
&[(recipient_keys.npk(), shared_secret)],
|
||||
&[],
|
||||
&Program::authenticated_transfer_program(),
|
||||
&Program::authenticated_transfer_program().into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -911,7 +914,7 @@ pub mod tests {
|
||||
sender_keys.nsk,
|
||||
state.get_proof_for_commitment(&sender_commitment).unwrap(),
|
||||
)],
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -963,7 +966,7 @@ pub mod tests {
|
||||
sender_keys.nsk,
|
||||
state.get_proof_for_commitment(&sender_commitment).unwrap(),
|
||||
)],
|
||||
&program,
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -1176,7 +1179,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1202,7 +1205,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1228,7 +1231,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1254,7 +1257,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1282,7 +1285,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.to_owned().into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::ProgramProveFailed(_))));
|
||||
@ -1308,7 +1311,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1343,7 +1346,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1369,7 +1372,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1404,7 +1407,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1441,7 +1444,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1482,7 +1485,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1516,7 +1519,7 @@ pub mod tests {
|
||||
&[0xdeadbeef1, 0xdeadbeef2],
|
||||
&private_account_keys,
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1557,7 +1560,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&private_account_auth,
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1605,7 +1608,7 @@ pub mod tests {
|
||||
&[0xdeadbeef1, 0xdeadbeef2],
|
||||
&private_account_keys,
|
||||
&private_account_auth,
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1651,7 +1654,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1698,7 +1701,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1744,7 +1747,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1790,7 +1793,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1834,7 +1837,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1863,7 +1866,7 @@ pub mod tests {
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1905,7 +1908,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1951,7 +1954,7 @@ pub mod tests {
|
||||
&[0xdeadbeef1, 0xdeadbeef2],
|
||||
&private_account_keys,
|
||||
&[(sender_keys.nsk, (0, vec![]))],
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -1997,7 +2000,7 @@ pub mod tests {
|
||||
),
|
||||
],
|
||||
&private_account_auth,
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -2088,7 +2091,7 @@ pub mod tests {
|
||||
(sender_keys.npk(), shared_secret),
|
||||
],
|
||||
&private_account_auth,
|
||||
&program,
|
||||
&program.into(),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||
@ -2131,7 +2134,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chained_call_succeeds() {
|
||||
fn test_public_chained_call() {
|
||||
let program = Program::chain_caller();
|
||||
let key = PrivateKey::try_new([1; 32]).unwrap();
|
||||
let from = AccountId::from(&PublicKey::new_from_private_key(&key));
|
||||
@ -2310,6 +2313,128 @@ pub mod tests {
|
||||
assert_eq!(to_post, expected_to_post);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_private_chained_call() {
|
||||
// Arrange
|
||||
let chain_caller = Program::chain_caller();
|
||||
let auth_transfers = Program::authenticated_transfer_program();
|
||||
let from_keys = test_private_account_keys_1();
|
||||
let to_keys = test_private_account_keys_2();
|
||||
let initial_balance = 100;
|
||||
let from_account = AccountWithMetadata::new(
|
||||
Account {
|
||||
program_owner: auth_transfers.id(),
|
||||
balance: initial_balance,
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&from_keys.npk(),
|
||||
);
|
||||
let to_account = AccountWithMetadata::new(
|
||||
Account {
|
||||
program_owner: auth_transfers.id(),
|
||||
..Account::default()
|
||||
},
|
||||
true,
|
||||
&to_keys.npk(),
|
||||
);
|
||||
|
||||
let from_commitment = Commitment::new(&from_keys.npk(), &from_account.account);
|
||||
let to_commitment = Commitment::new(&to_keys.npk(), &to_account.account);
|
||||
let mut state = V02State::new_with_genesis_accounts(
|
||||
&[],
|
||||
&[from_commitment.clone(), to_commitment.clone()],
|
||||
)
|
||||
.with_test_programs();
|
||||
let amount: u128 = 37;
|
||||
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
|
||||
amount,
|
||||
Program::authenticated_transfer_program().id(),
|
||||
1,
|
||||
None,
|
||||
);
|
||||
|
||||
let from_esk = [3; 32];
|
||||
let from_ss = SharedSecretKey::new(&from_esk, &from_keys.ivk());
|
||||
let from_epk = EphemeralPublicKey::from_scalar(from_esk);
|
||||
|
||||
let to_esk = [3; 32];
|
||||
let to_ss = SharedSecretKey::new(&to_esk, &to_keys.ivk());
|
||||
let to_epk = EphemeralPublicKey::from_scalar(to_esk);
|
||||
|
||||
let mut dependencies = HashMap::new();
|
||||
|
||||
dependencies.insert(auth_transfers.id(), auth_transfers);
|
||||
let program_with_deps = ProgramWithDependencies::new(chain_caller, dependencies);
|
||||
|
||||
let from_new_nonce = 0xdeadbeef1;
|
||||
let to_new_nonce = 0xdeadbeef2;
|
||||
|
||||
let from_expected_post = Account {
|
||||
balance: initial_balance - amount,
|
||||
nonce: from_new_nonce,
|
||||
..from_account.account.clone()
|
||||
};
|
||||
let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post);
|
||||
|
||||
let to_expected_post = Account {
|
||||
balance: amount,
|
||||
nonce: to_new_nonce,
|
||||
..to_account.account.clone()
|
||||
};
|
||||
let to_expected_commitment = Commitment::new(&to_keys.npk(), &to_expected_post);
|
||||
|
||||
// Act
|
||||
let (output, proof) = execute_and_prove(
|
||||
&[to_account, from_account],
|
||||
&Program::serialize_instruction(instruction).unwrap(),
|
||||
&[1, 1],
|
||||
&[from_new_nonce, to_new_nonce],
|
||||
&[(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)],
|
||||
&[
|
||||
(
|
||||
from_keys.nsk,
|
||||
state.get_proof_for_commitment(&from_commitment).unwrap(),
|
||||
),
|
||||
(
|
||||
to_keys.nsk,
|
||||
state.get_proof_for_commitment(&to_commitment).unwrap(),
|
||||
),
|
||||
],
|
||||
&program_with_deps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(to_keys.npk(), to_keys.ivk(), to_epk),
|
||||
(from_keys.npk(), from_keys.ivk(), from_epk),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let transaction = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
state
|
||||
.transition_from_privacy_preserving_transaction(&transaction)
|
||||
.unwrap();
|
||||
|
||||
// Assert
|
||||
assert!(
|
||||
state
|
||||
.get_proof_for_commitment(&from_expected_commitment)
|
||||
.is_some()
|
||||
);
|
||||
assert!(
|
||||
state
|
||||
.get_proof_for_commitment(&to_expected_commitment)
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pda_mechanism_with_pinata_token_program() {
|
||||
let pinata_token = Program::pinata_token();
|
||||
|
||||
@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write
|
||||
type Instruction = u128;
|
||||
|
||||
fn main() {
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance_to_burn,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance_to_burn,
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -17,5 +20,5 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.balance -= balance_to_burn;
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
}
|
||||
|
||||
@ -10,10 +10,13 @@ type Instruction = (u128, ProgramId, u32, Option<PdaSeed>);
|
||||
/// It permutes the order of the input accounts on the subsequent call
|
||||
/// The `ProgramId` in the instruction must be the program_id of the authenticated transfers program
|
||||
fn main() {
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed),
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
},
|
||||
instruction_words
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [recipient_pre, sender_pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -44,6 +47,7 @@ fn main() {
|
||||
}
|
||||
|
||||
write_nssa_outputs_with_chained_call(
|
||||
instruction_words,
|
||||
vec![sender_pre.clone(), recipient_pre.clone()],
|
||||
vec![
|
||||
AccountPostState::new(sender_pre.account),
|
||||
|
||||
@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write
|
||||
type Instruction = ();
|
||||
|
||||
fn main() {
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: _,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: _,
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -15,5 +18,5 @@ fn main() {
|
||||
|
||||
let account_post = AccountPostState::new_claimed(pre.account.clone());
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![account_post]);
|
||||
write_nssa_outputs(instruction_words, vec![pre], vec![account_post]);
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ type Instruction = Vec<u8>;
|
||||
|
||||
/// A program that modifies the account data by setting bytes sent in instruction.
|
||||
fn main() {
|
||||
let ProgramInput { pre_states, instruction: data } = read_nssa_inputs::<Instruction>();
|
||||
let (ProgramInput { pre_states, instruction: data }, instruction_words) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -15,5 +15,9 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.data = data.try_into().expect("provided data should fit into data limit");
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]);
|
||||
write_nssa_outputs(
|
||||
instruction_words,
|
||||
vec![pre],
|
||||
vec![AccountPostState::new_claimed(account_post)],
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ use nssa_core::{
|
||||
type Instruction = ();
|
||||
|
||||
fn main() {
|
||||
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
|
||||
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -16,6 +16,7 @@ fn main() {
|
||||
let account_pre = pre.account.clone();
|
||||
|
||||
write_nssa_outputs(
|
||||
instruction_words,
|
||||
vec![pre],
|
||||
vec![
|
||||
AccountPostState::new(account_pre),
|
||||
|
||||
@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState,
|
||||
type Instruction = ();
|
||||
|
||||
fn main() {
|
||||
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
|
||||
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -14,5 +14,5 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.balance += 1;
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write
|
||||
type Instruction = ();
|
||||
|
||||
fn main() {
|
||||
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
|
||||
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre1, pre2] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -12,5 +12,9 @@ fn main() {
|
||||
|
||||
let account_pre1 = pre1.account.clone();
|
||||
|
||||
write_nssa_outputs(vec![pre1, pre2], vec![AccountPostState::new(account_pre1)]);
|
||||
write_nssa_outputs(
|
||||
instruction_words,
|
||||
vec![pre1, pre2],
|
||||
vec![AccountPostState::new(account_pre1)],
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,32 +5,32 @@ use nssa_core::{
|
||||
|
||||
/// Initializes a default account under the ownership of this program.
|
||||
/// This is achieved by a noop.
|
||||
fn initialize_account(pre_state: AccountWithMetadata) {
|
||||
fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState {
|
||||
let account_to_claim = pre_state.account.clone();
|
||||
let is_authorized = pre_state.is_authorized;
|
||||
|
||||
// Continue only if the account to claim has default values
|
||||
if account_to_claim != Account::default() {
|
||||
return;
|
||||
panic!("Account is already initialized");
|
||||
}
|
||||
|
||||
// Continue only if the owner authorized this operation
|
||||
if !is_authorized {
|
||||
return;
|
||||
panic!("Missing required authorization");
|
||||
}
|
||||
|
||||
// Noop will result in account being claimed for this program
|
||||
write_nssa_outputs(
|
||||
vec![pre_state],
|
||||
vec![AccountPostState::new(account_to_claim)],
|
||||
);
|
||||
AccountPostState::new(account_to_claim)
|
||||
}
|
||||
|
||||
/// Transfers `balance_to_move` native balance from `sender` to `recipient`.
|
||||
fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) {
|
||||
fn transfer(
|
||||
sender: AccountWithMetadata,
|
||||
recipient: AccountWithMetadata,
|
||||
balance_to_move: u128,
|
||||
) -> Vec<AccountPostState> {
|
||||
// Continue only if the sender has authorized this operation
|
||||
if !sender.is_authorized {
|
||||
return;
|
||||
panic!("Missing required authorization");
|
||||
}
|
||||
|
||||
// This segment is a safe protection from authenticated transfer program
|
||||
@ -50,29 +50,33 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance
|
||||
sender_post.balance -= balance_to_move + malicious_offset;
|
||||
recipient_post.balance += balance_to_move + malicious_offset;
|
||||
|
||||
write_nssa_outputs(
|
||||
vec![sender, recipient],
|
||||
vec![
|
||||
AccountPostState::new(sender_post),
|
||||
AccountPostState::new(recipient_post),
|
||||
],
|
||||
);
|
||||
vec![
|
||||
AccountPostState::new(sender_post),
|
||||
AccountPostState::new(recipient_post),
|
||||
]
|
||||
}
|
||||
|
||||
/// A transfer of balance program.
|
||||
/// To be used both in public and private contexts.
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance_to_move,
|
||||
} = read_nssa_inputs();
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance_to_move,
|
||||
},
|
||||
instruction_data,
|
||||
) = read_nssa_inputs();
|
||||
|
||||
match (pre_states.as_slice(), balance_to_move) {
|
||||
([account_to_claim], 0) => initialize_account(account_to_claim.clone()),
|
||||
let post_states = match (pre_states.as_slice(), balance_to_move) {
|
||||
([account_to_claim], 0) => {
|
||||
let post = initialize_account(account_to_claim.clone());
|
||||
vec![post]
|
||||
}
|
||||
([sender, recipient], balance_to_move) => {
|
||||
transfer(sender.clone(), recipient.clone(), balance_to_move)
|
||||
}
|
||||
_ => panic!("invalid params"),
|
||||
}
|
||||
};
|
||||
write_nssa_outputs(instruction_data, pre_states, post_states);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState,
|
||||
type Instruction = ();
|
||||
|
||||
fn main() {
|
||||
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
|
||||
let (ProgramInput { pre_states, .. } , instruction_words) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -14,5 +14,5 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.nonce += 1;
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
write_nssa_outputs(instruction_words ,vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState,
|
||||
type Instruction = ();
|
||||
|
||||
fn main() {
|
||||
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
|
||||
let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -14,5 +14,5 @@ fn main() {
|
||||
let mut account_post = account_pre.clone();
|
||||
account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7];
|
||||
|
||||
write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]);
|
||||
}
|
||||
|
||||
@ -3,10 +3,13 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write
|
||||
type Instruction = u128;
|
||||
|
||||
fn main() {
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
let (
|
||||
ProgramInput {
|
||||
pre_states,
|
||||
instruction: balance,
|
||||
},
|
||||
instruction_words,
|
||||
) = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [sender_pre, receiver_pre] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
@ -19,6 +22,7 @@ fn main() {
|
||||
receiver_post.balance += balance;
|
||||
|
||||
write_nssa_outputs(
|
||||
instruction_words,
|
||||
vec![sender_pre, receiver_pre],
|
||||
vec![
|
||||
AccountPostState::new(sender_post),
|
||||
|
||||
@ -283,7 +283,7 @@ impl WalletCore {
|
||||
.map(|keys| (keys.npk.clone(), keys.ssk.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
&acc_manager.private_account_auth(),
|
||||
program,
|
||||
&program.to_owned().into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
161
wallet/src/pinata_interactions.rs
Normal file
161
wallet/src/pinata_interactions.rs
Normal file
@ -0,0 +1,161 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use nssa::{AccountId, privacy_preserving_transaction::circuit};
|
||||
use nssa_core::{MembershipProof, SharedSecretKey, account::AccountWithMetadata};
|
||||
|
||||
use crate::{
|
||||
WalletCore, helperfunctions::produce_random_nonces, transaction_utils::AccountPreparedData,
|
||||
};
|
||||
|
||||
impl WalletCore {
|
||||
pub async fn claim_pinata(
|
||||
&self,
|
||||
pinata_account_id: AccountId,
|
||||
winner_account_id: AccountId,
|
||||
solution: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let account_ids = vec![pinata_account_id, winner_account_id];
|
||||
let program_id = nssa::program::Program::pinata().id();
|
||||
let message =
|
||||
nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn claim_pinata_private_owned_account_already_initialized(
|
||||
&self,
|
||||
pinata_account_id: AccountId,
|
||||
winner_account_id: AccountId,
|
||||
solution: u128,
|
||||
winner_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: winner_nsk,
|
||||
npk: winner_npk,
|
||||
ipk: winner_ipk,
|
||||
auth_acc: winner_pre,
|
||||
proof: _,
|
||||
} = self
|
||||
.private_acc_preparation(winner_account_id, true, false)
|
||||
.await?;
|
||||
|
||||
let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap();
|
||||
|
||||
let program = nssa::program::Program::pinata();
|
||||
|
||||
let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id);
|
||||
|
||||
let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk);
|
||||
let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[pinata_pre, winner_pre],
|
||||
&nssa::program::Program::serialize_instruction(solution).unwrap(),
|
||||
&[0, 1],
|
||||
&produce_random_nonces(1),
|
||||
&[(winner_npk.clone(), shared_secret_winner.clone())],
|
||||
&[(winner_nsk.unwrap(), winner_proof)],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message =
|
||||
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
|
||||
vec![pinata_account_id],
|
||||
vec![],
|
||||
vec![(
|
||||
winner_npk.clone(),
|
||||
winner_ipk.clone(),
|
||||
eph_holder_winner.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
|
||||
&message,
|
||||
proof,
|
||||
&[],
|
||||
);
|
||||
let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new(
|
||||
message,
|
||||
witness_set,
|
||||
);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_winner],
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn claim_pinata_private_owned_account_not_initialized(
|
||||
&self,
|
||||
pinata_account_id: AccountId,
|
||||
winner_account_id: AccountId,
|
||||
solution: u128,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: _,
|
||||
npk: winner_npk,
|
||||
ipk: winner_ipk,
|
||||
auth_acc: winner_pre,
|
||||
proof: _,
|
||||
} = self
|
||||
.private_acc_preparation(winner_account_id, false, false)
|
||||
.await?;
|
||||
|
||||
let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap();
|
||||
|
||||
let program = nssa::program::Program::pinata();
|
||||
|
||||
let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id);
|
||||
|
||||
let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk);
|
||||
let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[pinata_pre, winner_pre],
|
||||
&nssa::program::Program::serialize_instruction(solution).unwrap(),
|
||||
&[0, 2],
|
||||
&produce_random_nonces(1),
|
||||
&[(winner_npk.clone(), shared_secret_winner.clone())],
|
||||
&[],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message =
|
||||
nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output(
|
||||
vec![pinata_account_id],
|
||||
vec![],
|
||||
vec![(
|
||||
winner_npk.clone(),
|
||||
winner_ipk.clone(),
|
||||
eph_holder_winner.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set =
|
||||
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
|
||||
&message,
|
||||
proof,
|
||||
&[],
|
||||
);
|
||||
let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new(
|
||||
message,
|
||||
witness_set,
|
||||
);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_winner],
|
||||
))
|
||||
}
|
||||
}
|
||||
592
wallet/src/transaction_utils.rs
Normal file
592
wallet/src/transaction_utils.rs
Normal file
@ -0,0 +1,592 @@
|
||||
use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse};
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use nssa::{
|
||||
Account, AccountId, PrivacyPreservingTransaction,
|
||||
privacy_preserving_transaction::{circuit, message::Message, witness_set::WitnessSet},
|
||||
program::Program,
|
||||
};
|
||||
use nssa_core::{
|
||||
Commitment, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
account::AccountWithMetadata, encryption::IncomingViewingPublicKey, program::InstructionData,
|
||||
};
|
||||
|
||||
use crate::{WalletCore, helperfunctions::produce_random_nonces};
|
||||
|
||||
pub(crate) struct AccountPreparedData {
|
||||
pub nsk: Option<NullifierSecretKey>,
|
||||
pub npk: NullifierPublicKey,
|
||||
pub ipk: IncomingViewingPublicKey,
|
||||
pub auth_acc: AccountWithMetadata,
|
||||
pub proof: Option<MembershipProof>,
|
||||
}
|
||||
|
||||
impl WalletCore {
|
||||
pub(crate) async fn private_acc_preparation(
|
||||
&self,
|
||||
account_id: AccountId,
|
||||
is_authorized: bool,
|
||||
needs_proof: bool,
|
||||
) -> Result<AccountPreparedData, ExecutionFailureKind> {
|
||||
let Some((from_keys, from_acc)) = self
|
||||
.storage
|
||||
.user_data
|
||||
.get_private_account(&account_id)
|
||||
.cloned()
|
||||
else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let mut nsk = None;
|
||||
let mut proof = None;
|
||||
|
||||
let from_npk = from_keys.nullifer_public_key;
|
||||
let from_ipk = from_keys.incoming_viewing_public_key;
|
||||
|
||||
let sender_commitment = Commitment::new(&from_npk, &from_acc);
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), is_authorized, &from_npk);
|
||||
|
||||
if is_authorized {
|
||||
nsk = Some(from_keys.private_key_holder.nullifier_secret_key);
|
||||
}
|
||||
|
||||
if needs_proof {
|
||||
proof = self
|
||||
.sequencer_client
|
||||
.get_proof_for_commitment(sender_commitment)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(AccountPreparedData {
|
||||
nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn private_tx_two_accs_all_init(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
to_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: from_nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: from_proof,
|
||||
} = self.private_acc_preparation(from, true, true).await?;
|
||||
|
||||
let AccountPreparedData {
|
||||
nsk: to_nsk,
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
auth_acc: recipient_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(to, true, false).await?;
|
||||
|
||||
tx_pre_check(&sender_pre.account, &recipient_pre.account)?;
|
||||
|
||||
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
|
||||
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
|
||||
|
||||
let eph_holder_to = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[1, 1],
|
||||
&produce_random_nonces(2),
|
||||
&[
|
||||
(from_npk.clone(), shared_secret_from.clone()),
|
||||
(to_npk.clone(), shared_secret_to.clone()),
|
||||
],
|
||||
&[
|
||||
(from_nsk.unwrap(), from_proof.unwrap()),
|
||||
(to_nsk.unwrap(), to_proof),
|
||||
],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder_from.generate_ephemeral_public_key(),
|
||||
),
|
||||
(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder_to.generate_ephemeral_public_key(),
|
||||
),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_from, shared_secret_to],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn private_tx_two_accs_receiver_uninit(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: from_nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: from_proof,
|
||||
} = self.private_acc_preparation(from, true, true).await?;
|
||||
|
||||
let AccountPreparedData {
|
||||
nsk: _,
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
auth_acc: recipient_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(to, false, false).await?;
|
||||
|
||||
tx_pre_check(&sender_pre.account, &recipient_pre.account)?;
|
||||
|
||||
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
|
||||
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
|
||||
|
||||
let eph_holder_to = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[1, 2],
|
||||
&produce_random_nonces(2),
|
||||
&[
|
||||
(from_npk.clone(), shared_secret_from.clone()),
|
||||
(to_npk.clone(), shared_secret_to.clone()),
|
||||
],
|
||||
&[(from_nsk.unwrap(), from_proof.unwrap())],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder_from.generate_ephemeral_public_key(),
|
||||
),
|
||||
(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder_to.generate_ephemeral_public_key(),
|
||||
),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_from, shared_secret_to],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn private_tx_two_accs_receiver_outer(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_ipk: IncomingViewingPublicKey,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: from_nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: from_proof,
|
||||
} = self.private_acc_preparation(from, true, true).await?;
|
||||
|
||||
let to_acc = nssa_core::account::Account::default();
|
||||
|
||||
tx_pre_check(&sender_pre.account, &to_acc)?;
|
||||
|
||||
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&to_npk);
|
||||
|
||||
let shared_secret_from = eph_holder.calculate_shared_secret_sender(&from_ipk);
|
||||
let shared_secret_to = eph_holder.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[1, 2],
|
||||
&produce_random_nonces(2),
|
||||
&[
|
||||
(from_npk.clone(), shared_secret_from.clone()),
|
||||
(to_npk.clone(), shared_secret_to.clone()),
|
||||
],
|
||||
&[(from_nsk.unwrap(), from_proof.unwrap())],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
),
|
||||
(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_from, shared_secret_to],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn deshielded_tx_two_accs(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: from_nsk,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: from_proof,
|
||||
} = self.private_acc_preparation(from, true, true).await?;
|
||||
|
||||
let Ok(to_acc) = self.get_account_public(to).await else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
tx_pre_check(&sender_pre.account, &to_acc)?;
|
||||
|
||||
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, to);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&from_npk);
|
||||
let shared_secret = eph_holder.calculate_shared_secret_sender(&from_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[1, 0],
|
||||
&produce_random_nonces(1),
|
||||
&[(from_npk.clone(), shared_secret.clone())],
|
||||
&[(from_nsk.unwrap(), from_proof.unwrap())],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![to],
|
||||
vec![],
|
||||
vec![(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn shielded_two_accs_all_init(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
to_proof: MembershipProof,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let Ok(from_acc) = self.get_account_public(from).await else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let AccountPreparedData {
|
||||
nsk: to_nsk,
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
auth_acc: recipient_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(to, true, false).await?;
|
||||
|
||||
tx_pre_check(&from_acc, &recipient_pre.account)?;
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[0, 1],
|
||||
&produce_random_nonces(1),
|
||||
&[(to_npk.clone(), shared_secret.clone())],
|
||||
&[(to_nsk.unwrap(), to_proof)],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![from],
|
||||
vec![from_acc.nonce],
|
||||
vec![(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
|
||||
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn shielded_two_accs_receiver_uninit(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to: AccountId,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let Ok(from_acc) = self.get_account_public(from).await else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let AccountPreparedData {
|
||||
nsk: _,
|
||||
npk: to_npk,
|
||||
ipk: to_ipk,
|
||||
auth_acc: recipient_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(to, false, false).await?;
|
||||
|
||||
tx_pre_check(&from_acc, &recipient_pre.account)?;
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[0, 2],
|
||||
&produce_random_nonces(1),
|
||||
&[(to_npk.clone(), shared_secret.clone())],
|
||||
&[],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![from],
|
||||
vec![from_acc.nonce],
|
||||
vec![(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
|
||||
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn shielded_two_accs_receiver_outer(
|
||||
&self,
|
||||
from: AccountId,
|
||||
to_npk: NullifierPublicKey,
|
||||
to_ipk: IncomingViewingPublicKey,
|
||||
instruction_data: InstructionData,
|
||||
tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>,
|
||||
program: Program,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let Ok(from_acc) = self.get_account_public(from).await else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let to_acc = Account::default();
|
||||
|
||||
tx_pre_check(&from_acc, &to_acc)?;
|
||||
|
||||
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from);
|
||||
let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk);
|
||||
|
||||
let eph_holder = EphemeralKeyHolder::new(&to_npk);
|
||||
let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk);
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre, recipient_pre],
|
||||
&instruction_data,
|
||||
&[0, 2],
|
||||
&produce_random_nonces(1),
|
||||
&[(to_npk.clone(), shared_secret.clone())],
|
||||
&[],
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![from],
|
||||
vec![from_acc.nonce],
|
||||
vec![(
|
||||
to_npk.clone(),
|
||||
to_ipk.clone(),
|
||||
eph_holder.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
};
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_private(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn register_account_under_authenticated_transfers_programs_private(
|
||||
&self,
|
||||
from: AccountId,
|
||||
) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> {
|
||||
let AccountPreparedData {
|
||||
nsk: _,
|
||||
npk: from_npk,
|
||||
ipk: from_ipk,
|
||||
auth_acc: sender_pre,
|
||||
proof: _,
|
||||
} = self.private_acc_preparation(from, false, false).await?;
|
||||
|
||||
let eph_holder_from = EphemeralKeyHolder::new(&from_npk);
|
||||
let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk);
|
||||
|
||||
let instruction: u128 = 0;
|
||||
|
||||
let (output, proof) = circuit::execute_and_prove(
|
||||
&[sender_pre],
|
||||
&Program::serialize_instruction(instruction).unwrap(),
|
||||
&[2],
|
||||
&produce_random_nonces(1),
|
||||
&[(from_npk.clone(), shared_secret_from.clone())],
|
||||
&[],
|
||||
&Program::authenticated_transfer_program().into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message = Message::try_from_circuit_output(
|
||||
vec![],
|
||||
vec![],
|
||||
vec![(
|
||||
from_npk.clone(),
|
||||
from_ipk.clone(),
|
||||
eph_holder_from.generate_ephemeral_public_key(),
|
||||
)],
|
||||
output,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
Ok((
|
||||
self.sequencer_client.send_tx_private(tx).await?,
|
||||
[shared_secret_from],
|
||||
))
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user