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 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue