goas: atomic transfer scenario

This commit is contained in:
David Rusu 2024-08-10 21:09:36 +04:00
parent 1e49131c12
commit d73508a43c
7 changed files with 339 additions and 42 deletions

View File

@ -57,7 +57,18 @@ impl StateWitness {
} }
} }
pub fn withdraw(mut self, w: Withdraw) -> Self { pub fn apply(self, tx: Tx) -> Self {
let mut state = match tx {
Tx::Withdraw(w) => self.withdraw(w),
Tx::Deposit(d) => self.deposit(d),
};
state.included_txs.push(tx);
state
}
fn withdraw(mut self, w: Withdraw) -> Self {
let Withdraw { from, amount } = w; let Withdraw { from, amount } = w;
let from_balance = self.balances.entry(from).or_insert(0); let from_balance = self.balances.entry(from).or_insert(0);
@ -65,12 +76,10 @@ impl StateWitness {
.checked_sub(amount) .checked_sub(amount)
.expect("insufficient funds in account"); .expect("insufficient funds in account");
self.included_txs.push(Tx::Withdraw(w));
self self
} }
pub fn deposit(mut self, d: Deposit) -> Self { fn deposit(mut self, d: Deposit) -> Self {
let Deposit { to, amount } = d; let Deposit { to, amount } = d;
let to_balance = self.balances.entry(to).or_insert(0); let to_balance = self.balances.entry(to).or_insert(0);
@ -78,7 +87,6 @@ impl StateWitness {
.checked_add(amount) .checked_add(amount)
.expect("overflow in account balance"); .expect("overflow in account balance");
self.included_txs.push(Tx::Deposit(d));
self self
} }

View File

@ -1,5 +1,16 @@
use common::{BoundTx, StateWitness, ZoneMetadata}; use cl::{PartialTxInputWitness, PartialTxOutputWitness};
use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate}; use common::{BoundTx, IncludedTxWitness, StateRoots, StateWitness, ZoneMetadata};
use goas_proof_statements::{
user_note::{UserAtomicTransfer, UserIntent},
zone_funds::SpendFundsPrivate,
zone_state::ZoneStatePrivate,
};
pub fn user_atomic_transfer_death_constraint() -> [u8; 32] {
ledger::death_constraint::risc0_id_to_cl_death_constraint(
goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID,
)
}
pub fn zone_state_death_constraint() -> [u8; 32] { pub fn zone_state_death_constraint() -> [u8; 32] {
ledger::death_constraint::risc0_id_to_cl_death_constraint(goas_risc0_proofs::ZONE_STATE_ID) ledger::death_constraint::risc0_id_to_cl_death_constraint(goas_risc0_proofs::ZONE_STATE_ID)
@ -82,3 +93,46 @@ pub fn prove_zone_fund_withdraw(
let receipt = prove_info.receipt; let receipt = prove_info.receipt;
ledger::DeathProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt) ledger::DeathProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt)
} }
pub fn prove_user_atomic_transfer(
user_note: PartialTxInputWitness,
user_intent: UserIntent,
zone_a: PartialTxOutputWitness,
zone_b: PartialTxOutputWitness,
zone_a_roots: StateRoots,
zone_b_roots: StateRoots,
withdraw_tx: IncludedTxWitness,
deposit_tx: IncludedTxWitness,
) -> ledger::DeathProof {
let private_inputs = UserAtomicTransfer {
user_note,
user_intent,
zone_a,
zone_b,
zone_a_roots,
zone_b_roots,
withdraw_tx,
deposit_tx,
};
let env = risc0_zkvm::ExecutorEnv::builder()
.write(&private_inputs)
.unwrap()
.build()
.unwrap();
let prover = risc0_zkvm::default_prover();
use std::time::Instant;
let start_t = Instant::now();
let opts = risc0_zkvm::ProverOpts::succinct();
let prove_info = prover
.prove_with_opts(env, goas_risc0_proofs::USER_ATOMIC_TRANSFER_ELF, &opts)
.unwrap();
println!(
"STARK 'user atomic transfer' prover time: {:.2?}",
start_t.elapsed()
);
let receipt = prove_info.receipt;
ledger::DeathProof::from_risc0(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, receipt)
}

View File

@ -0,0 +1,257 @@
use std::collections::BTreeMap;
use cl::{BundleWitness, NoteWitness, NullifierNonce};
use common::{BoundTx, Deposit, StateWitness, Tx, Withdraw, ZoneMetadata};
use goas_proof_statements::user_note::UserIntent;
use rand_core::CryptoRngCore;
fn zone_fund_utxo(
value: u64,
zone_meta: ZoneMetadata,
mut rng: impl CryptoRngCore,
) -> cl::OutputWitness {
cl::OutputWitness::public(
cl::NoteWitness {
value,
unit: *common::ZONE_CL_FUNDS_UNIT,
death_constraint: zone_meta.funds_vk,
state: zone_meta.id(),
},
NullifierNonce::random(&mut rng),
)
}
fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness {
cl::OutputWitness::public(
cl::NoteWitness {
value: 1,
unit: zone.zone_metadata.unit,
death_constraint: zone.zone_metadata.zone_vk,
state: zone.commit().0,
},
NullifierNonce::random(&mut rng),
)
}
#[derive(Debug, Clone)]
struct ZoneNotes {
state: StateWitness,
state_note: cl::OutputWitness,
fund_note: cl::OutputWitness,
}
impl ZoneNotes {
fn new_with_balances(
zone_name: &str,
balances: BTreeMap<u32, u64>,
mut rng: impl CryptoRngCore,
) -> Self {
let state = StateWitness {
balances,
included_txs: vec![],
zone_metadata: executor::zone_metadata(zone_name),
nonce: [0; 32],
};
let state_note = zone_state_utxo(&state, &mut rng);
let fund_note = zone_fund_utxo(state.total_balance(), state.zone_metadata, &mut rng);
Self {
state,
state_note,
fund_note,
}
}
fn state_input_witness(&self) -> cl::InputWitness {
cl::InputWitness::public(self.state_note)
}
fn fund_input_witness(&self) -> cl::InputWitness {
cl::InputWitness::public(self.fund_note)
}
fn run(mut self, txs: Vec<Tx>) -> Self {
for tx in txs {
self.state = self.state.apply(tx);
}
self.state = self.state.evolve_nonce();
let state_in = self.state_input_witness();
self.state_note = cl::OutputWitness::public(
cl::NoteWitness {
state: self.state.commit().0,
..state_in.note
},
state_in.evolved_nonce(),
);
let fund_in = self.fund_input_witness();
self.fund_note = cl::OutputWitness::public(
cl::NoteWitness {
value: self.state.total_balance(),
..fund_in.note
},
NullifierNonce::from_bytes(self.state.nonce),
);
self
}
}
#[test]
fn test_atomic_transfer() {
let mut rng = rand::thread_rng();
let alice = 42;
let zone_a_start =
ZoneNotes::new_with_balances("ZONE_A", BTreeMap::from_iter([(alice, 100)]), &mut rng);
let zone_b_start = ZoneNotes::new_with_balances("ZONE_B", BTreeMap::from_iter([]), &mut rng);
let alice_intent = UserIntent {
zone_a_meta: zone_a_start.state.zone_metadata,
zone_b_meta: zone_b_start.state.zone_metadata,
withdraw: Withdraw {
from: alice,
amount: 75,
},
deposit: Deposit {
to: alice,
amount: 75,
},
};
let alice_intent_out = cl::OutputWitness::public(
NoteWitness {
value: 1,
unit: cl::note::unit_point("INTENT"),
death_constraint: executor::user_atomic_transfer_death_constraint(),
state: alice_intent.commit(),
},
NullifierNonce::random(&mut rng),
);
let user_ptx = cl::PartialTxWitness {
inputs: vec![],
outputs: vec![alice_intent_out],
};
let zone_a_end = zone_a_start
.clone()
.run(vec![Tx::Withdraw(alice_intent.withdraw)]);
let zone_b_end = zone_b_start
.clone()
.run(vec![Tx::Deposit(alice_intent.deposit)]);
let alice_intent_in = cl::InputWitness::public(alice_intent_out);
let atomic_transfer_ptx = cl::PartialTxWitness {
inputs: vec![
alice_intent_in,
zone_a_start.state_input_witness(),
zone_a_start.fund_input_witness(),
zone_b_start.state_input_witness(),
zone_b_start.fund_input_witness(),
],
outputs: vec![
zone_a_end.state_note,
zone_a_end.fund_note,
zone_b_end.state_note,
zone_b_end.fund_note,
],
};
let death_proofs = BTreeMap::from_iter([
(
alice_intent_in.nullifier(),
executor::prove_user_atomic_transfer(
atomic_transfer_ptx.input_witness(0),
alice_intent,
atomic_transfer_ptx.output_witness(0),
atomic_transfer_ptx.output_witness(2),
zone_a_end.state.state_roots(),
zone_b_end.state.state_roots(),
zone_a_end.state.included_tx_witness(0),
zone_b_end.state.included_tx_witness(0),
),
),
(
zone_a_start.state_input_witness().nullifier(),
executor::prove_zone_stf(
zone_a_start.state.clone(),
vec![BoundTx {
tx: Tx::Withdraw(alice_intent.withdraw),
bind: atomic_transfer_ptx.input_witness(0), // input intent note
}],
atomic_transfer_ptx.input_witness(1), // input state note
atomic_transfer_ptx.output_witness(0), // output state note
atomic_transfer_ptx.output_witness(1), // output funds note
),
),
(
zone_a_start.fund_input_witness().nullifier(),
executor::prove_zone_fund_withdraw(
atomic_transfer_ptx.input_witness(2), // input fund note
atomic_transfer_ptx.output_witness(0), // output state note
&zone_a_end.state,
),
),
(
zone_b_start.state_input_witness().nullifier(),
executor::prove_zone_stf(
zone_b_start.state.clone(),
vec![BoundTx {
tx: Tx::Deposit(alice_intent.deposit),
bind: atomic_transfer_ptx.input_witness(0), // input intent note
}],
atomic_transfer_ptx.input_witness(3), // input state note
atomic_transfer_ptx.output_witness(2), // output state note
atomic_transfer_ptx.output_witness(3), // output funds note
),
),
(
zone_b_start.fund_input_witness().nullifier(),
executor::prove_zone_fund_withdraw(
atomic_transfer_ptx.input_witness(4), // input fund note (input #1)
atomic_transfer_ptx.output_witness(2), // output state note (output #0)
&zone_b_end.state,
),
),
]);
let user_ptx_proof =
ledger::partial_tx::ProvedPartialTx::prove(&user_ptx, BTreeMap::new(), &[])
.expect("user ptx failed to prove");
assert!(user_ptx_proof.verify());
let note_commitments = vec![
alice_intent_out.commit_note(),
zone_a_start.state_note.commit_note(),
zone_a_start.fund_note.commit_note(),
zone_b_start.state_note.commit_note(),
zone_b_start.fund_note.commit_note(),
];
let atomic_transfer_proof = ledger::partial_tx::ProvedPartialTx::prove(
&atomic_transfer_ptx,
death_proofs,
&note_commitments,
)
.expect("atomic transfer proof failed");
assert!(atomic_transfer_proof.verify());
let bundle = cl::Bundle {
partials: vec![user_ptx.commit(), atomic_transfer_ptx.commit()],
};
let bundle_witness = BundleWitness {
balance_blinding: cl::BalanceWitness(
user_ptx.balance_blinding().0 + atomic_transfer_ptx.balance_blinding().0,
),
};
let bundle_proof =
ledger::bundle::ProvedBundle::prove(&bundle, &bundle_witness).expect("bundle proof failed");
assert!(bundle_proof.verify());
}

View File

@ -47,7 +47,10 @@ fn test_deposit() {
amount: 78, amount: 78,
}; };
let end_state = init_state.clone().deposit(deposit).evolve_nonce(); let end_state = init_state
.clone()
.apply(Tx::Deposit(deposit))
.evolve_nonce();
let zone_state_out = cl::OutputWitness::public( let zone_state_out = cl::OutputWitness::public(
cl::NoteWitness { cl::NoteWitness {

View File

@ -66,7 +66,10 @@ fn test_withdrawal() {
amount: 78, amount: 78,
}; };
let end_state = init_state.clone().withdraw(withdraw.clone()).evolve_nonce(); let end_state = init_state
.clone()
.apply(Tx::Withdraw(withdraw.clone()))
.evolve_nonce();
let zone_state_out = cl::OutputWitness::public( let zone_state_out = cl::OutputWitness::public(
cl::NoteWitness { cl::NoteWitness {

View File

@ -26,7 +26,7 @@ use ledger_proof_statements::death_constraint::DeathConstraintPublic;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct UserIntent { pub struct UserIntent {
pub zone_a_meta: common::ZoneMetadata, pub zone_a_meta: common::ZoneMetadata,
pub zone_b_meta: common::ZoneMetadata, pub zone_b_meta: common::ZoneMetadata,

View File

@ -1,5 +1,5 @@
use cl::{ use cl::{
note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, PartialTxInputWitness, note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness,
PtxRoot, PtxRoot,
}; };
@ -8,26 +8,6 @@ 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 withdraw(
state: StateWitness,
input_root: [u8; 32],
withdrawal: Withdraw,
bind: PartialTxInputWitness,
) -> StateWitness {
assert_eq!(bind.input_root(), input_root);
state.withdraw(withdrawal)
}
fn deposit(
state: StateWitness,
input_root: [u8; 32],
deposit: Deposit,
bind: PartialTxInputWitness,
) -> StateWitness {
assert_eq!(bind.input_root(), input_root);
state.deposit(deposit)
}
fn validate_zone_transition( fn validate_zone_transition(
in_note: cl::PartialTxInputWitness, in_note: cl::PartialTxInputWitness,
out_note: cl::PartialTxOutputWitness, out_note: cl::PartialTxOutputWitness,
@ -98,17 +78,9 @@ fn main() {
let in_state_cm = state.commit(); let in_state_cm = state.commit();
for input in inputs { for BoundTx { tx, bind } in inputs {
state = match input { assert_eq!(bind.input_root(), input_root);
BoundTx { state = state.apply(tx)
tx: Tx::Withdraw(w),
bind,
} => withdraw(state, input_root, w, bind),
BoundTx {
tx: Tx::Deposit(d),
bind,
} => deposit(state, input_root, d, bind),
}
} }
let state = state.evolve_nonce(); let state = state.evolve_nonce();