bind proofs (#22)

* bind proofs

* refactor to remove cycle
This commit is contained in:
Giacomo Pasini 2024-08-09 17:33:01 +02:00 committed by GitHub
parent 85a3e941b9
commit 1aa7a7f81f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 72 additions and 80 deletions

View File

@ -1,4 +1,4 @@
use cl::{balance::Unit, merkle, nullifier::NullifierCommitment};
use cl::{balance::Unit, merkle, PartialTxInputWitness};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
@ -62,31 +62,27 @@ impl StateWitness {
}
pub fn withdraw(mut self, w: Withdraw) -> Self {
self.included_txs.push(Tx::Withdraw(w));
let Withdraw {
from,
amount,
to: _,
} = w;
let Withdraw { from, amount } = w;
let from_balance = self.balances.entry(from).or_insert(0);
*from_balance = from_balance
.checked_sub(amount)
.expect("insufficient funds in account");
self.included_txs.push(Tx::Withdraw(w));
self
}
pub fn deposit(mut self, d: Deposit) -> Self {
self.included_txs.push(Tx::Deposit(d));
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.included_txs.push(Tx::Deposit(d));
self
}
@ -148,15 +144,13 @@ impl From<StateCommitment> for [u8; 32] {
pub struct Withdraw {
pub from: AccountId,
pub amount: u64,
pub to: NullifierCommitment,
}
impl Withdraw {
pub fn to_bytes(&self) -> [u8; 44] {
let mut bytes = [0; 44];
pub fn to_bytes(&self) -> [u8; 12] {
let mut bytes = [0; 12];
bytes[0..4].copy_from_slice(&self.from.to_le_bytes());
bytes[4..12].copy_from_slice(&self.amount.to_le_bytes());
bytes[12..44].copy_from_slice(self.to.as_bytes());
bytes
}
}
@ -169,15 +163,31 @@ pub struct Deposit {
}
impl Deposit {
pub fn to_bytes(&self) -> [u8; 32] {
let mut bytes = [0; 32];
pub fn to_bytes(&self) -> [u8; 12] {
let mut bytes = [0; 12];
bytes[0..4].copy_from_slice(&self.to.to_le_bytes());
bytes[4..12].copy_from_slice(&self.amount.to_le_bytes());
bytes
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
/// A Tx that is executed in the zone if and only if the bind is
/// present is the same partial transaction
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BoundTx {
pub tx: Tx,
pub bind: PartialTxInputWitness,
}
impl BoundTx {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.tx.to_bytes();
bytes.extend(self.bind.input.commit().to_bytes());
bytes
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Tx {
Withdraw(Withdraw),
Deposit(Deposit),

View File

@ -1,15 +1,12 @@
use common::{StateWitness, Tx};
use common::{BoundTx, StateWitness};
use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate};
use std::collections::VecDeque;
pub fn prove_zone_stf(
state: StateWitness,
inputs: Vec<Tx>,
inputs: Vec<BoundTx>,
zone_in: cl::PartialTxInputWitness,
zone_out: cl::PartialTxOutputWitness,
funds_out: cl::PartialTxOutputWitness,
withdrawals: VecDeque<cl::PartialTxOutputWitness>,
deposits: VecDeque<cl::PartialTxInputWitness>,
) -> ledger::DeathProof {
let private_inputs = ZoneStatePrivate {
state,
@ -17,8 +14,6 @@ pub fn prove_zone_stf(
zone_in,
zone_out,
funds_out,
withdrawals,
deposits,
};
let env = risc0_zkvm::ExecutorEnv::builder()

View File

@ -1,7 +1,7 @@
use std::collections::{BTreeMap, VecDeque};
use std::collections::BTreeMap;
use cl::{NoteWitness, NullifierNonce, NullifierSecret};
use common::{StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT};
use common::{BoundTx, StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT};
use ledger::death_constraint::DeathProof;
use rand_core::CryptoRngCore;
@ -65,13 +65,22 @@ fn test_withdrawal() {
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 alice_intent = cl::InputWitness::random(
cl::OutputWitness::random(
NoteWitness::stateless(1, *ZONE_CL_FUNDS_UNIT, DeathProof::nop_constraint()), // TODO, intent should be in the death constraint
alice_sk.commit(),
&mut rng,
),
alice_sk,
&mut rng,
);
let withdraw = common::Withdraw {
from: alice,
amount: 78,
to: alice_sk.commit(),
};
let end_state = init_state.clone().withdraw(withdraw).evolve_nonce();
let end_state = init_state.clone().withdraw(withdraw.clone()).evolve_nonce();
let zone_state_out = cl::OutputWitness::public(
cl::NoteWitness {
@ -99,7 +108,7 @@ fn test_withdrawal() {
);
let withdraw_ptx = cl::PartialTxWitness {
inputs: vec![zone_state_in, zone_fund_in],
inputs: vec![zone_state_in, zone_fund_in, alice_intent],
outputs: vec![zone_state_out, zone_fund_out, alice_withdrawal],
};
@ -108,12 +117,13 @@ fn test_withdrawal() {
zone_state_in.nullifier(),
executor::prove_zone_stf(
init_state.clone(),
vec![Tx::Withdraw(withdraw)],
vec![BoundTx {
tx: Tx::Withdraw(withdraw.clone()),
bind: withdraw_ptx.input_witness(2),
}],
withdraw_ptx.input_witness(0), // input state note (input #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
),
),
(
@ -124,11 +134,16 @@ fn test_withdrawal() {
&end_state,
),
),
(
alice_intent.nullifier(),
DeathProof::prove_nop(alice_intent.nullifier(), withdraw_ptx.commit().root()),
),
]);
let note_commitments = vec![
zone_state_in.note_commitment(),
zone_fund_in.note_commitment(),
alice_intent.note_commitment(),
];
let withdraw_proof =

View File

@ -1,11 +1,10 @@
use common::{StateWitness, Tx};
use common::{BoundTx, StateWitness};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ZoneStatePrivate {
pub state: StateWitness,
pub inputs: Vec<Tx>,
pub inputs: Vec<BoundTx>,
pub zone_in: cl::PartialTxInputWitness,
pub zone_out: cl::PartialTxOutputWitness,
/// While the absence of birth constraints does not guarantee uniqueness of a note that can be used as
@ -13,8 +12,4 @@ pub struct ZoneStatePrivate {
/// 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

@ -1,8 +1,5 @@
use cl::{
note::NoteWitness,
nullifier::NullifierNonce,
output::OutputWitness,
partial_tx::{PartialTxInputWitness, PartialTxOutputWitness},
note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, PartialTxInputWitness,
PtxRoot,
};
@ -13,46 +10,22 @@ use risc0_zkvm::guest::env;
fn withdraw(
state: StateWitness,
output_root: [u8; 32],
withdrawal_req: Withdraw,
withdrawal: PartialTxOutputWitness,
input_root: [u8; 32],
withdrawal: Withdraw,
bind: PartialTxInputWitness,
) -> StateWitness {
// 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);
assert_eq!(output_root, withdrawal.output_root());
state.withdraw(withdrawal_req)
assert_eq!(bind.input_root(), input_root);
state.withdraw(withdrawal)
}
fn deposit(
state: StateWitness,
input_root: [u8; 32],
deposit_req: Deposit,
deposit: PartialTxInputWitness,
deposit: Deposit,
bind: PartialTxInputWitness,
) -> StateWitness {
assert_eq!(deposit.input_root(), input_root);
// 1) Check the deposit note is not already under control of the zone
assert_ne!(
deposit.input.note.death_constraint,
state.zone_metadata.funds_vk
);
// 2) Check the deposit note is for the correct amount
assert_eq!(deposit.input.note.unit, *ZONE_CL_FUNDS_UNIT);
assert_eq!(deposit.input.note.value, deposit_req.amount);
// 3) Check the deposit note is for the correct recipient
assert_eq!(
AccountId::from_le_bytes(<[u8; 4]>::try_from(&deposit.input.note.state[..4]).unwrap()),
deposit_req.to
);
state.deposit(deposit_req)
assert_eq!(bind.input_root(), input_root);
state.deposit(deposit)
}
fn validate_zone_transition(
@ -113,8 +86,6 @@ fn main() {
zone_in,
zone_out,
funds_out,
mut withdrawals,
mut deposits,
} = env::read();
let input_root = zone_in.input_root();
@ -129,8 +100,14 @@ fn main() {
for input in inputs {
state = match input {
Tx::Withdraw(w) => withdraw(state, output_root, w, withdrawals.pop_front().unwrap()),
Tx::Deposit(d) => deposit(state, input_root, d, deposits.pop_front().unwrap()),
BoundTx {
tx: Tx::Withdraw(w),
bind,
} => withdraw(state, input_root, w, bind),
BoundTx {
tx: Tx::Deposit(d),
bind,
} => deposit(state, input_root, d, bind),
}
}