goas: atomic transfer scenario
This commit is contained in:
parent
1e49131c12
commit
d73508a43c
|
@ -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 from_balance = self.balances.entry(from).or_insert(0);
|
||||
|
@ -65,12 +76,10 @@ impl StateWitness {
|
|||
.checked_sub(amount)
|
||||
.expect("insufficient funds in account");
|
||||
|
||||
self.included_txs.push(Tx::Withdraw(w));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn deposit(mut self, d: Deposit) -> Self {
|
||||
fn deposit(mut self, d: Deposit) -> Self {
|
||||
let Deposit { to, amount } = d;
|
||||
|
||||
let to_balance = self.balances.entry(to).or_insert(0);
|
||||
|
@ -78,7 +87,6 @@ impl StateWitness {
|
|||
.checked_add(amount)
|
||||
.expect("overflow in account balance");
|
||||
|
||||
self.included_txs.push(Tx::Deposit(d));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
use common::{BoundTx, StateWitness, ZoneMetadata};
|
||||
use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate};
|
||||
use cl::{PartialTxInputWitness, PartialTxOutputWitness};
|
||||
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] {
|
||||
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;
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
¬e_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());
|
||||
}
|
|
@ -47,7 +47,10 @@ fn test_deposit() {
|
|||
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(
|
||||
cl::NoteWitness {
|
||||
|
|
|
@ -66,7 +66,10 @@ fn test_withdrawal() {
|
|||
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(
|
||||
cl::NoteWitness {
|
||||
|
|
|
@ -26,7 +26,7 @@ use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct UserIntent {
|
||||
pub zone_a_meta: common::ZoneMetadata,
|
||||
pub zone_b_meta: common::ZoneMetadata,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use cl::{
|
||||
note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, PartialTxInputWitness,
|
||||
note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness,
|
||||
PtxRoot,
|
||||
};
|
||||
|
||||
|
@ -8,26 +8,6 @@ use goas_proof_statements::zone_state::ZoneStatePrivate;
|
|||
use ledger_proof_statements::death_constraint::DeathConstraintPublic;
|
||||
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(
|
||||
in_note: cl::PartialTxInputWitness,
|
||||
out_note: cl::PartialTxOutputWitness,
|
||||
|
@ -98,17 +78,9 @@ fn main() {
|
|||
|
||||
let in_state_cm = state.commit();
|
||||
|
||||
for input in inputs {
|
||||
state = match input {
|
||||
BoundTx {
|
||||
tx: Tx::Withdraw(w),
|
||||
bind,
|
||||
} => withdraw(state, input_root, w, bind),
|
||||
BoundTx {
|
||||
tx: Tx::Deposit(d),
|
||||
bind,
|
||||
} => deposit(state, input_root, d, bind),
|
||||
}
|
||||
for BoundTx { tx, bind } in inputs {
|
||||
assert_eq!(bind.input_root(), input_root);
|
||||
state = state.apply(tx)
|
||||
}
|
||||
|
||||
let state = state.evolve_nonce();
|
||||
|
|
Loading…
Reference in New Issue