Refactor zone auth (#19)

* Refactor zone auth

* remove redundant check
This commit is contained in:
Giacomo Pasini 2024-08-08 17:50:28 +02:00 committed by GitHub
parent a320c20d25
commit 5d3f3ab9fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 161 additions and 299 deletions

View File

@ -1,36 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Event {
Spend(Spend),
}
impl Event {
pub fn to_bytes(&self) -> Vec<u8> {
// TODO: add variant tag to byte encoding
match self {
Event::Spend(spend) => spend.to_bytes().to_vec(),
}
}
}
/// An event that authorizes spending zone funds
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Spend {
pub amount: u64,
/// The public key of the recipient
pub to: cl::NullifierCommitment,
/// The nullifier of note that is being spent, this is to avoid using the spend event to
/// for multiple notes
pub fund_nf: cl::Nullifier,
}
impl Spend {
pub fn to_bytes(&self) -> [u8; 72] {
let mut bytes = [0; 72];
bytes[0..8].copy_from_slice(&self.amount.to_le_bytes());
bytes[8..40].copy_from_slice(self.to.as_bytes());
bytes[40..72].copy_from_slice(self.fund_nf.as_bytes());
bytes
}
}

View File

@ -1,11 +1,4 @@
pub mod events; use cl::{balance::Unit, nullifier::NullifierCommitment};
use cl::{
balance::Unit,
input::InputWitness,
nullifier::{Nullifier, NullifierCommitment},
output::OutputWitness,
};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@ -46,8 +39,8 @@ impl ZoneMetadata {
pub struct StateWitness { pub struct StateWitness {
pub balances: BTreeMap<AccountId, u64>, pub balances: BTreeMap<AccountId, u64>,
pub included_txs: Vec<Input>, pub included_txs: Vec<Input>,
pub output_events: Vec<events::Event>,
pub zone_metadata: ZoneMetadata, pub zone_metadata: ZoneMetadata,
pub nonce: [u8; 32],
} }
impl StateWitness { impl StateWitness {
@ -56,10 +49,10 @@ impl StateWitness {
/// / \ /// / \
/// io state /// io state
/// / \ / \ /// / \ / \
/// events txs zoneid balances /// nonce txs zoneid balances
pub fn commit(&self) -> StateCommitment { pub fn commit(&self) -> StateCommitment {
let root = cl::merkle::root([ let root = cl::merkle::root([
self.events_root(), self.nonce,
self.included_txs_root(), self.included_txs_root(),
self.zone_metadata.id(), self.zone_metadata.id(),
self.balances_root(), self.balances_root(),
@ -74,8 +67,7 @@ impl StateWitness {
let Withdraw { let Withdraw {
from, from,
amount, amount,
to, to: _,
fund_nf,
} = w; } = w;
let from_balance = self.balances.entry(from).or_insert(0); let from_balance = self.balances.entry(from).or_insert(0);
@ -83,20 +75,19 @@ impl StateWitness {
.checked_sub(amount) .checked_sub(amount)
.expect("insufficient funds in account"); .expect("insufficient funds in account");
let spend_auth = events::Spend {
amount,
to,
fund_nf,
};
self.output_events.push(events::Event::Spend(spend_auth));
self self
} }
pub fn events_root(&self) -> [u8; 32] { pub fn deposit(mut self, d: Deposit) -> Self {
let event_bytes = Vec::from_iter(self.output_events.iter().map(events::Event::to_bytes)); self.included_txs.push(Input::Deposit(d));
let event_merkle_leaves = cl::merkle::padded_leaves(&event_bytes);
cl::merkle::root::<MAX_EVENTS>(event_merkle_leaves) let Deposit { to, amount } = d;
let to_balance = self.balances.entry(to).or_insert(0);
*to_balance += to_balance
.checked_add(amount)
.expect("overflow in account balance");
self
} }
pub fn included_txs_root(&self) -> [u8; 32] { pub fn included_txs_root(&self) -> [u8; 32] {
@ -117,11 +108,21 @@ impl StateWitness {
cl::merkle::root::<MAX_BALANCES>(balance_merkle_leaves) cl::merkle::root::<MAX_BALANCES>(balance_merkle_leaves)
} }
pub fn event_merkle_path(&self, event: events::Event) -> Vec<cl::merkle::PathNode> { pub fn total_balance(&self) -> u64 {
let idx = self.output_events.iter().position(|e| e == &event).unwrap(); self.balances.values().sum()
let event_bytes = Vec::from_iter(self.output_events.iter().map(events::Event::to_bytes)); }
let event_merkle_leaves = cl::merkle::padded_leaves(&event_bytes);
cl::merkle::path::<MAX_EVENTS>(event_merkle_leaves, idx) pub fn evolve_nonce(self) -> Self {
let updated_nonce = {
let mut hasher = Sha256::new();
hasher.update(&self.nonce);
hasher.update(b"NOMOS_ZONE_NONCE_EVOLVE");
hasher.finalize().into()
};
Self {
nonce: updated_nonce,
..self
}
} }
} }
@ -136,24 +137,14 @@ pub struct Withdraw {
pub from: AccountId, pub from: AccountId,
pub amount: u64, pub amount: u64,
pub to: NullifierCommitment, pub to: NullifierCommitment,
pub fund_nf: Nullifier,
} }
impl Withdraw { impl Withdraw {
pub fn to_event(&self) -> events::Spend { pub fn to_bytes(&self) -> [u8; 44] {
events::Spend { let mut bytes = [0; 44];
amount: self.amount,
to: self.to,
fund_nf: self.fund_nf,
}
}
pub fn to_bytes(&self) -> [u8; 72] {
let mut bytes = [0; 72];
bytes[0..4].copy_from_slice(&self.from.to_le_bytes()); bytes[0..4].copy_from_slice(&self.from.to_le_bytes());
bytes[4..8].copy_from_slice(&self.amount.to_le_bytes()); bytes[4..12].copy_from_slice(&self.amount.to_le_bytes());
bytes[8..40].copy_from_slice(self.to.as_bytes()); bytes[12..44].copy_from_slice(self.to.as_bytes());
bytes[40..72].copy_from_slice(self.fund_nf.as_bytes());
bytes bytes
} }
} }
@ -161,16 +152,17 @@ impl Withdraw {
/// A deposit of funds into the zone /// A deposit of funds into the zone
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Deposit { pub struct Deposit {
/// The note that is used to deposit funds into the zone pub to: AccountId,
pub deposit: InputWitness, pub amount: u64,
}
// This zone state note impl Deposit {
pub zone_note_in: InputWitness, pub fn to_bytes(&self) -> [u8; 32] {
pub zone_note_out: OutputWitness, let mut bytes = [0; 32];
bytes[0..4].copy_from_slice(&self.to.to_le_bytes());
// The zone funds note bytes[4..12].copy_from_slice(&self.amount.to_le_bytes());
pub zone_funds_in: InputWitness, bytes
pub zone_funds_out: OutputWitness, }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,17 +1,24 @@
use common::{events::Event, Input, StateWitness}; use common::{Input, StateWitness};
use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate}; use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate};
use std::collections::VecDeque;
pub fn prove_zone_stf( pub fn prove_zone_stf(
state: StateWitness, state: StateWitness,
inputs: Vec<Input>, inputs: Vec<Input>,
zone_in: cl::PartialTxInputWitness, zone_in: cl::PartialTxInputWitness,
zone_out: cl::PartialTxOutputWitness, zone_out: cl::PartialTxOutputWitness,
funds_out: cl::PartialTxOutputWitness,
withdrawals: VecDeque<cl::PartialTxOutputWitness>,
deposits: VecDeque<cl::PartialTxInputWitness>,
) -> ledger::DeathProof { ) -> ledger::DeathProof {
let private_inputs = ZoneStatePrivate { let private_inputs = ZoneStatePrivate {
state, state,
inputs, inputs,
zone_in, zone_in,
zone_out, zone_out,
funds_out,
withdrawals,
deposits,
}; };
let env = risc0_zkvm::ExecutorEnv::builder() let env = risc0_zkvm::ExecutorEnv::builder()
@ -36,21 +43,12 @@ pub fn prove_zone_stf(
pub fn prove_zone_fund_withdraw( pub fn prove_zone_fund_withdraw(
in_zone_funds: cl::PartialTxInputWitness, in_zone_funds: cl::PartialTxInputWitness,
zone_note: cl::PartialTxOutputWitness, zone_note: cl::PartialTxOutputWitness,
out_zone_funds: cl::PartialTxOutputWitness,
spent_note: cl::PartialTxOutputWitness,
out_zone_state: &StateWitness, out_zone_state: &StateWitness,
withdraw: common::Withdraw,
) -> ledger::DeathProof { ) -> ledger::DeathProof {
let spend_event = withdraw.to_event();
let private_inputs = SpendFundsPrivate { let private_inputs = SpendFundsPrivate {
in_zone_funds, in_zone_funds,
zone_note, zone_note,
out_zone_funds, state_witness: out_zone_state.clone(),
spent_note,
spend_event,
spend_event_state_path: out_zone_state.event_merkle_path(Event::Spend(spend_event)),
balances_root: out_zone_state.balances_root(),
txs_root: out_zone_state.included_txs_root(),
}; };
let env = risc0_zkvm::ExecutorEnv::builder() let env = risc0_zkvm::ExecutorEnv::builder()

View File

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::{BTreeMap, VecDeque};
use cl::{NoteWitness, NullifierNonce, NullifierSecret}; use cl::{NoteWitness, NullifierNonce, NullifierSecret};
use common::{events::Event, Input, StateWitness, ZoneMetadata, ZONE_CL_FUNDS_UNIT}; use common::{Input, StateWitness, ZoneMetadata, ZONE_CL_FUNDS_UNIT};
use ledger::death_constraint::DeathProof; use ledger::death_constraint::DeathProof;
use rand_core::CryptoRngCore; use rand_core::CryptoRngCore;
@ -53,26 +53,25 @@ fn test_withdrawal() {
let init_state = StateWitness { let init_state = StateWitness {
balances: BTreeMap::from_iter([(alice, 100)]), balances: BTreeMap::from_iter([(alice, 100)]),
included_txs: vec![], included_txs: vec![],
output_events: vec![],
zone_metadata: ZoneMetadata { zone_metadata: ZoneMetadata {
zone_vk: zone_state_death_constraint(), zone_vk: zone_state_death_constraint(),
funds_vk: zone_fund_death_constraint(), funds_vk: zone_fund_death_constraint(),
unit: cl::note::unit_point("ZONE_STATE"), unit: cl::note::unit_point("ZONE_STATE"),
}, },
nonce: [0; 32],
}; };
let zone_fund_in = let zone_fund_in =
cl::InputWitness::public(zone_fund_utxo(35240, init_state.zone_metadata, &mut rng)); cl::InputWitness::public(zone_fund_utxo(100, init_state.zone_metadata, &mut rng));
let zone_state_in = cl::InputWitness::public(zone_state_utxo(&init_state, &mut rng)); let zone_state_in = cl::InputWitness::public(zone_state_utxo(&init_state, &mut rng));
let withdraw = common::Withdraw { let withdraw = common::Withdraw {
from: alice, from: alice,
amount: 78, amount: 78,
to: alice_sk.commit(), to: alice_sk.commit(),
fund_nf: zone_fund_in.nullifier(),
}; };
let end_state = init_state.clone().withdraw(withdraw); let end_state = init_state.clone().withdraw(withdraw).evolve_nonce();
let zone_state_out = cl::OutputWitness::public( let zone_state_out = cl::OutputWitness::public(
cl::NoteWitness { cl::NoteWitness {
@ -86,7 +85,7 @@ fn test_withdrawal() {
value: zone_fund_in.note.value - withdraw.amount, value: zone_fund_in.note.value - withdraw.amount,
..zone_fund_in.note ..zone_fund_in.note
}, },
zone_fund_in.evolved_nonce(), NullifierNonce::from_bytes(end_state.nonce),
); );
let alice_withdrawal = cl::OutputWitness::random( let alice_withdrawal = cl::OutputWitness::random(
@ -112,6 +111,9 @@ fn test_withdrawal() {
vec![Input::Withdraw(withdraw)], vec![Input::Withdraw(withdraw)],
withdraw_ptx.input_witness(0), // input state note (input #0) withdraw_ptx.input_witness(0), // input state note (input #0)
withdraw_ptx.output_witness(0), // output state note (output #0) withdraw_ptx.output_witness(0), // output state note (output #0)
withdraw_ptx.output_witness(1), // output funds note (output #1)
VecDeque::from_iter([withdraw_ptx.output_witness(2)]), // alice withdrawal
VecDeque::new(), // no deposits
), ),
), ),
( (
@ -119,10 +121,7 @@ fn test_withdrawal() {
executor::prove_zone_fund_withdraw( executor::prove_zone_fund_withdraw(
withdraw_ptx.input_witness(1), // input fund note (input #1) withdraw_ptx.input_witness(1), // input fund note (input #1)
withdraw_ptx.output_witness(0), // output state note (output #0) withdraw_ptx.output_witness(0), // output state note (output #0)
withdraw_ptx.output_witness(1), // output state note (output #0)
withdraw_ptx.output_witness(2), // output state note (output #0)
&end_state, &end_state,
withdraw,
), ),
), ),
]); ]);
@ -144,12 +143,8 @@ fn test_withdrawal() {
StateWitness { StateWitness {
balances: BTreeMap::from_iter([(alice, 22)]), balances: BTreeMap::from_iter([(alice, 22)]),
included_txs: vec![Input::Withdraw(withdraw)], included_txs: vec![Input::Withdraw(withdraw)],
output_events: vec![Event::Spend(common::events::Spend { zone_metadata: init_state.zone_metadata,
amount: 78, nonce: init_state.evolve_nonce().nonce,
to: alice_sk.commit(),
fund_nf: zone_fund_in.nullifier()
})],
zone_metadata: init_state.zone_metadata
} }
.commit() .commit()
.0 .0

View File

@ -6,16 +6,6 @@ pub struct SpendFundsPrivate {
pub in_zone_funds: cl::PartialTxInputWitness, pub in_zone_funds: cl::PartialTxInputWitness,
/// The zone note that is authorizing the spend /// The zone note that is authorizing the spend
pub zone_note: cl::PartialTxOutputWitness, pub zone_note: cl::PartialTxOutputWitness,
/// The note that is being created to send the change back to the zone /// The state of the zone
pub out_zone_funds: cl::PartialTxOutputWitness, pub state_witness: common::StateWitness,
/// The spent funds note
pub spent_note: cl::PartialTxOutputWitness,
/// The event emitted by the zone that authorizes the spend
pub spend_event: common::events::Spend,
/// Path to the zone output events root
pub spend_event_state_path: Vec<cl::merkle::PathNode>,
/// Merkle root of txs included in the zone
pub txs_root: [u8; 32],
/// Merkle root of balances in the zone
pub balances_root: [u8; 32],
} }

View File

@ -1,5 +1,6 @@
use common::{Input, StateWitness}; use common::{Input, StateWitness};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ZoneStatePrivate { pub struct ZoneStatePrivate {
@ -7,4 +8,13 @@ pub struct ZoneStatePrivate {
pub inputs: Vec<Input>, pub inputs: Vec<Input>,
pub zone_in: cl::PartialTxInputWitness, pub zone_in: cl::PartialTxInputWitness,
pub zone_out: cl::PartialTxOutputWitness, pub zone_out: cl::PartialTxOutputWitness,
/// While the absence of birth constraints does not guarantee uniqueness of a note that can be used as
/// zone funds, deposits and withdrawals make sure the funds are merged in a single note.
/// This means that while there's nothing to prevent creation of notes with the same characteristics of zone
/// funds, those would not be tracked by the zone state and can be ignored.
pub funds_out: cl::PartialTxOutputWitness,
/// Each note is the result of the execution of a withdrawal request
pub withdrawals: VecDeque<cl::PartialTxOutputWitness>,
/// Each note is providing funds for a deposit request
pub deposits: VecDeque<cl::PartialTxInputWitness>,
} }

View File

@ -10,87 +10,23 @@ use risc0_zkvm::guest::env;
fn main() { fn main() {
let SpendFundsPrivate { let SpendFundsPrivate {
in_zone_funds, in_zone_funds,
out_zone_funds,
zone_note, zone_note,
spent_note, state_witness,
spend_event,
spend_event_state_path,
txs_root,
balances_root,
} = env::read(); } = env::read();
let input_root = in_zone_funds.input_root(); let input_root = in_zone_funds.input_root();
let output_root = out_zone_funds.output_root(); let output_root = zone_note.output_root();
assert_eq!(output_root, zone_note.output_root());
assert_eq!(output_root, spent_note.output_root());
assert_eq!(output_root, out_zone_funds.output_root());
// ** Assert the spent event was an output of the correct zone stf **
// The zone state field is a merkle tree over:
// root
// / \
// io state
// / \ / \
// events txs zoneid balances
// We need to check that:
// 1) There is a valid path from the spend event to the events root
// 2) The zone id matches the one in the current funds note state
// 3) The witnesses for spend path, txs and balances allow to calculate the correct root
let zone_id = in_zone_funds.input.note.state; // TODO: is there more state?
let spend_event_leaf = merkle::leaf(&spend_event.to_bytes());
let event_root = merkle::path_root(spend_event_leaf, &spend_event_state_path);
assert_eq!(
merkle::root([event_root, txs_root, zone_id, balances_root]),
zone_note.output.note.state
);
// Check we return the rest of the funds back to the zone
let change = in_zone_funds
.input
.note
.value
.checked_sub(spend_event.amount)
.unwrap();
assert_eq!(out_zone_funds.output.note.value, change);
// zone funds output should have the same death constraints as the zone funds input
assert_eq!(
in_zone_funds.input.note.death_constraint,
out_zone_funds.output.note.death_constraint
);
assert_eq!(
in_zone_funds.input.note.unit,
out_zone_funds.output.note.unit
);
// ensure zone fund sk's, blindings and nonces are propagated correctly.
assert_eq!(
in_zone_funds.input.nf_sk.commit(),
out_zone_funds.output.nf_pk
);
assert_eq!(
in_zone_funds.input.balance_blinding,
out_zone_funds.output.balance_blinding
);
assert_eq!(
in_zone_funds.input.evolved_nonce(),
out_zone_funds.output.nonce,
);
// the state is propagated
assert_eq!(
in_zone_funds.input.note.state,
out_zone_funds.output.note.state,
);
// check the correct amount of funds is being spent
assert_eq!(spent_note.output.note.value, spend_event.amount);
assert_eq!(spent_note.output.note.unit, in_zone_funds.input.note.unit);
// check the correct recipient is being paid
assert_eq!(spent_note.output.nf_pk, spend_event.to);
let nf = in_zone_funds.input.nullifier();
assert_eq!(nf, spend_event.fund_nf); // ensure this event was meant for this note.
let ptx_root = PtxRoot(merkle::node(input_root, output_root)); let ptx_root = PtxRoot(merkle::node(input_root, output_root));
// 1) Check the zone note is the correct one
assert_eq!(
in_zone_funds.input.note.state,
state_witness.zone_metadata.id()
);
assert_eq!(zone_note.output.note.state, state_witness.commit().0);
let nf = in_zone_funds.input.nullifier();
env::commit(&DeathConstraintPublic { ptx_root, nf }); env::commit(&DeathConstraintPublic { ptx_root, nf });
} }

View File

@ -1,7 +1,8 @@
use cl::{ use cl::{
merkle, note::NoteWitness,
nullifier::{Nullifier, NullifierSecret}, nullifier::NullifierNonce,
partial_tx::{MAX_INPUTS, MAX_OUTPUTS}, output::OutputWitness,
partial_tx::{PartialTxInputWitness, PartialTxOutputWitness},
PtxRoot, PtxRoot,
}; };
@ -10,114 +11,70 @@ use goas_proof_statements::zone_state::ZoneStatePrivate;
use ledger_proof_statements::death_constraint::DeathConstraintPublic; use ledger_proof_statements::death_constraint::DeathConstraintPublic;
use risc0_zkvm::guest::env; use risc0_zkvm::guest::env;
fn deposit( fn withdraw(
mut state: StateWitness, state: StateWitness,
deposit: Deposit, output_root: [u8; 32],
pub_inputs: DeathConstraintPublic, withdrawal_req: Withdraw,
withdrawal: PartialTxOutputWitness,
) -> StateWitness { ) -> StateWitness {
state.included_txs.push(Input::Deposit(deposit.clone())); // 1) check the correct amount of funds is being spent
assert_eq!(withdrawal.output.note.value, withdrawal_req.amount);
assert_eq!(withdrawal.output.note.unit, *ZONE_CL_FUNDS_UNIT);
// 2) check the correct recipient is being paid
assert_eq!(withdrawal.output.nf_pk, withdrawal_req.to);
let Deposit { assert_eq!(output_root, withdrawal.output_root());
deposit,
zone_note_in,
zone_note_out,
zone_funds_in,
zone_funds_out,
} = deposit;
let funds_vk = state.zone_metadata.funds_vk; state.withdraw(withdrawal_req)
}
// 1) Check there are no more input/output notes than expected fn deposit(
let inputs = [ state: StateWitness,
deposit.commit().to_bytes().to_vec(), input_root: [u8; 32],
zone_note_in.commit().to_bytes().to_vec(), deposit_req: Deposit,
zone_funds_in.commit().to_bytes().to_vec(), deposit: PartialTxInputWitness,
]; ) -> StateWitness {
assert_eq!(deposit.input_root(), input_root);
let inputs_root = merkle::root(merkle::padded_leaves::<MAX_INPUTS>(&inputs)); // 1) Check the deposit note is not already under control of the zone
assert_ne!(
let outputs = [ deposit.input.note.death_constraint,
zone_note_out.commit().to_bytes().to_vec(), state.zone_metadata.funds_vk
zone_funds_out.commit().to_bytes().to_vec(),
];
let outputs_root = merkle::root(merkle::padded_leaves::<MAX_OUTPUTS>(&outputs));
let ptx_root = PtxRoot(merkle::node(inputs_root, outputs_root));
assert_eq!(ptx_root, pub_inputs.ptx_root);
// 2) Check the deposit note is not already under control of the zone
assert_ne!(deposit.note.death_constraint, funds_vk);
// 3) Check the ptx is balanced. This is not a requirement for standard ptxs, but we need it
// in deposits (at least in a first version) to ensure fund tracking
assert_eq!(deposit.note.unit, *ZONE_CL_FUNDS_UNIT);
assert_eq!(zone_funds_in.note.unit, *ZONE_CL_FUNDS_UNIT);
assert_eq!(zone_funds_out.note.unit, *ZONE_CL_FUNDS_UNIT);
let in_sum = deposit.note.value + zone_funds_in.note.value;
let out_sum = zone_note_out.note.value;
assert_eq!(out_sum, in_sum, "deposit ptx is unbalanced");
// 4) Check the zone fund notes are correctly created
assert_eq!(zone_funds_in.note.death_constraint, funds_vk);
assert_eq!(zone_funds_out.note.death_constraint, funds_vk);
assert_eq!(zone_funds_in.note.state, state.zone_metadata.id());
assert_eq!(zone_funds_out.note.state, state.zone_metadata.id());
assert_eq!(zone_funds_in.nf_sk, NullifierSecret::from_bytes([0; 16])); // there is no secret in the zone funds
assert_eq!(zone_funds_out.nf_pk, zone_funds_in.nf_sk.commit()); // the sk is the same
// nonce is correctly evolved
assert_eq!(zone_funds_out.nonce, zone_funds_in.evolved_nonce());
// 5) Check zone state notes are correctly created
assert_eq!(
zone_note_in.note.death_constraint,
zone_note_out.note.death_constraint
); );
assert_eq!(zone_note_in.nf_sk, NullifierSecret::from_bytes([0; 16])); //// there is no secret in the zone state
assert_eq!(zone_note_out.nf_pk, zone_note_in.nf_sk.commit()); // the sk is the same
assert_eq!(zone_note_in.note.unit, zone_note_out.note.unit);
assert_eq!(zone_note_in.note.value, zone_note_out.note.value);
// nonce is correctly evolved
assert_eq!(zone_note_out.nonce, zone_note_in.evolved_nonce());
let nullifier = Nullifier::new(zone_note_in.nf_sk, zone_note_in.nonce);
assert_eq!(nullifier, pub_inputs.nf);
// 6) We're now ready to do the deposit! // 2) Check the deposit note is for the correct amount
let amount = deposit.note.value; assert_eq!(deposit.input.note.unit, *ZONE_CL_FUNDS_UNIT);
let to = AccountId::from_be_bytes(<[u8; 4]>::try_from(&deposit.note.state[0..4]).unwrap()); assert_eq!(deposit.input.note.value, deposit_req.amount);
let to_balance = state.balances.entry(to).or_insert(0); // 3) Check the deposit note is for the correct recipient
*to_balance = to_balance assert_eq!(
.checked_add(amount) AccountId::from_le_bytes(<[u8; 4]>::try_from(&deposit.input.note.state[..4]).unwrap()),
.expect("overflow when depositing"); deposit_req.to
);
state state.deposit(deposit_req)
} }
fn validate_zone_transition( fn validate_zone_transition(
in_note: cl::PartialTxInputWitness, in_note: cl::PartialTxInputWitness,
out_note: cl::PartialTxOutputWitness, out_note: cl::PartialTxOutputWitness,
in_meta: ZoneMetadata, out_funds: cl::PartialTxOutputWitness,
in_state_cm: StateCommitment, in_state_cm: StateCommitment,
out_state: StateWitness, out_state: StateWitness,
) { ) {
let metadata = out_state.zone_metadata;
let out_state_cm = out_state.commit().0;
// Ensure input/output notes are committing to the expected states. // Ensure input/output notes are committing to the expected states.
assert_eq!(in_note.input.note.state, in_state_cm.0); assert_eq!(in_note.input.note.state, in_state_cm.0);
assert_eq!(out_note.output.note.state, out_state.commit().0); assert_eq!(out_note.output.note.state, out_state_cm);
// zone metadata is propagated
assert_eq!(out_state.zone_metadata.id(), in_meta.id());
// ensure units match metadata // ensure units match metadata
assert_eq!(in_note.input.note.unit, in_meta.unit); assert_eq!(in_note.input.note.unit, metadata.unit);
assert_eq!(out_note.output.note.unit, in_meta.unit); assert_eq!(out_note.output.note.unit, metadata.unit);
// ensure constraints match metadata // ensure constraints match metadata
assert_eq!(in_note.input.note.death_constraint, in_meta.zone_vk); assert_eq!(in_note.input.note.death_constraint, metadata.zone_vk);
assert_eq!(out_note.output.note.death_constraint, in_meta.zone_vk); assert_eq!(out_note.output.note.death_constraint, metadata.zone_vk);
// nullifier secret is propagated // nullifier secret is propagated
assert_eq!(in_note.input.nf_sk.commit(), out_note.output.nf_pk); assert_eq!(in_note.input.nf_sk.commit(), out_note.output.nf_pk);
@ -130,6 +87,23 @@ fn validate_zone_transition(
// the nonce is correctly evolved // the nonce is correctly evolved
assert_eq!(in_note.input.evolved_nonce(), out_note.output.nonce); assert_eq!(in_note.input.evolved_nonce(), out_note.output.nonce);
// funds are still under control of the zone
let expected_note_witness = NoteWitness::new(
out_state.total_balance(),
*ZONE_CL_FUNDS_UNIT,
metadata.funds_vk,
metadata.id(),
);
assert_eq!(
out_funds.output,
OutputWitness::public(
expected_note_witness,
NullifierNonce::from_bytes(out_state.nonce)
)
);
// funds belong to the same partial tx
assert_eq!(out_funds.output_root(), out_note.output_root());
} }
fn main() { fn main() {
@ -138,27 +112,30 @@ fn main() {
inputs, inputs,
zone_in, zone_in,
zone_out, zone_out,
funds_out,
mut withdrawals,
mut deposits,
} = env::read(); } = env::read();
let input_root = zone_in.input_root();
let output_root = zone_out.output_root();
let pub_inputs = DeathConstraintPublic { let pub_inputs = DeathConstraintPublic {
ptx_root: PtxRoot(cl::merkle::node( ptx_root: PtxRoot(cl::merkle::node(input_root, output_root)),
zone_in.input_root(),
zone_out.output_root(),
)),
nf: zone_in.input.nullifier(), nf: zone_in.input.nullifier(),
}; };
let in_meta = state.zone_metadata;
let in_state_cm = state.commit(); let in_state_cm = state.commit();
for input in inputs { for input in inputs {
state = match input { state = match input {
Input::Withdraw(input) => state.withdraw(input), Input::Withdraw(w) => withdraw(state, output_root, w, withdrawals.pop_front().unwrap()),
Input::Deposit(input) => deposit(state, input, pub_inputs), Input::Deposit(d) => deposit(state, input_root, d, deposits.pop_front().unwrap()),
} }
} }
validate_zone_transition(zone_in, zone_out, in_meta, in_state_cm, state); let state = state.evolve_nonce();
validate_zone_transition(zone_in, zone_out, funds_out, in_state_cm, state);
env::commit(&pub_inputs); env::commit(&pub_inputs);
} }