2024-08-27 15:57:19 +04:00

355 lines
11 KiB
Rust

use std::collections::BTreeMap;
use cl::Constraint;
use common::{
mmr::MMR, AccountId, IncludedTxWitness, SignedBoundTx, StateWitness, Tx, ZoneMetadata,
};
use goas_proof_statements::{
user_note::UserAtomicTransfer, zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate,
};
use rand_core::CryptoRngCore;
#[derive(Debug, Clone)]
pub struct ZoneNotes {
pub state: StateWitness,
pub state_note: cl::OutputWitness,
pub fund_note: cl::OutputWitness,
}
impl ZoneNotes {
pub fn new_with_balances(
zone_name: &str,
balances: BTreeMap<AccountId, u64>,
mut rng: impl CryptoRngCore,
) -> Self {
let state = StateWitness {
balances,
included_txs: MMR::new(),
zone_metadata: zone_metadata(zone_name),
};
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,
}
}
pub fn state_input_witness(&self) -> cl::InputWitness {
cl::InputWitness::public(self.state_note)
}
pub fn fund_input_witness(&self) -> cl::InputWitness {
cl::InputWitness::public(self.fund_note)
}
pub fn run(mut self, tx: Tx) -> (Self, IncludedTxWitness) {
let (new_state, included_tx) = self.state.apply(tx);
self.state = new_state;
let state_in = self.state_input_witness();
self.state_note = cl::OutputWitness::public(cl::NoteWitness {
state: self.state.commit().0,
nonce: state_in.evolved_nonce(b"STATE_NONCE"),
..state_in.note
});
let fund_in = self.fund_input_witness();
self.fund_note = cl::OutputWitness::public(cl::NoteWitness {
value: self.state.total_balance(),
nonce: state_in.evolved_nonce(b"FUND_NONCE"),
..fund_in.note
});
(self, included_tx)
}
}
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,
constraint: zone_meta.funds_constraint,
state: zone_meta.id(),
nonce: cl::Nonce::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,
constraint: zone.zone_metadata.zone_constraint,
state: zone.commit().0,
nonce: cl::Nonce::random(&mut rng),
})
}
pub fn user_atomic_transfer_constraint() -> Constraint {
ledger::constraint::risc0_constraint(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID)
}
pub fn zone_state_constraint() -> Constraint {
ledger::constraint::risc0_constraint(goas_risc0_proofs::ZONE_STATE_ID)
}
pub fn zone_fund_constraint() -> Constraint {
ledger::constraint::risc0_constraint(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID)
}
pub fn zone_metadata(zone_mnemonic: &str) -> ZoneMetadata {
ZoneMetadata {
zone_constraint: zone_state_constraint(),
funds_constraint: zone_fund_constraint(),
unit: cl::note::derive_unit(zone_mnemonic),
}
}
pub fn prove_zone_stf(
state: StateWitness,
inputs: Vec<(SignedBoundTx, cl::PartialTxInputWitness)>,
zone_in: cl::PartialTxInputWitness,
zone_out: cl::PartialTxOutputWitness,
funds_out: cl::PartialTxOutputWitness,
) -> ledger::ConstraintProof {
let private_inputs = ZoneStatePrivate {
state,
inputs,
zone_in,
zone_out,
funds_out,
};
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::ZONE_STATE_ELF, &opts)
.unwrap();
println!(
"STARK 'zone_stf' prover time: {:.2?}, total_cycles: {}",
start_t.elapsed(),
prove_info.stats.total_cycles
);
let receipt = prove_info.receipt;
ledger::ConstraintProof::from_risc0(goas_risc0_proofs::ZONE_STATE_ID, receipt)
}
pub fn prove_zone_fund_constraint(
in_zone_funds: cl::PartialTxInputWitness,
zone_note: cl::PartialTxOutputWitness,
out_zone_state: &StateWitness,
) -> ledger::ConstraintProof {
let private_inputs = SpendFundsPrivate {
in_zone_funds,
zone_note,
state_roots: out_zone_state.state_roots(),
};
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::SPEND_ZONE_FUNDS_ELF, &opts)
.unwrap();
println!(
"STARK 'zone_fund' prover time: {:.2?}, total_cycles: {}",
start_t.elapsed(),
prove_info.stats.total_cycles
);
let receipt = prove_info.receipt;
ledger::ConstraintProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt)
}
pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger::ConstraintProof {
let env = risc0_zkvm::ExecutorEnv::builder()
.write(&atomic_transfer)
.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?}, total_cycles: {}",
start_t.elapsed(),
prove_info.stats.total_cycles
);
let receipt = prove_info.receipt;
ledger::ConstraintProof::from_risc0(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, receipt)
}
#[cfg(test)]
mod tests {
use cl::{
note::derive_unit, BalanceWitness, Nonce, NoteWitness, OutputWitness, PartialTxWitness,
};
use common::{BoundTx, Deposit, Withdraw};
use goas_proof_statements::user_note::UserIntent;
use ledger::ConstraintProof;
use ledger_proof_statements::constraint::ConstraintPublic;
use super::*;
#[test]
pub fn test_prove_zone_stf() {
let mut rng = rand::thread_rng();
let mut alice = common::new_account(&mut rng);
let alice_vk = alice.verifying_key().to_bytes();
let zone_start =
ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 32)]), &mut rng);
let bind = OutputWitness::public(NoteWitness::basic(
32,
*common::ZONE_CL_FUNDS_UNIT,
&mut rng,
));
let signed_withdraw = SignedBoundTx::sign(
BoundTx {
tx: Tx::Withdraw(Withdraw {
from: alice_vk,
amount: 10,
}),
bind: bind.commit_note(),
},
&mut alice,
);
let zone_end = zone_start.clone().run(signed_withdraw.bound_tx.tx).0;
let ptx = PartialTxWitness {
inputs: vec![
cl::InputWitness::public(bind),
zone_start.state_input_witness(),
zone_start.fund_input_witness(),
],
outputs: vec![zone_end.state_note, zone_end.fund_note],
balance_blinding: BalanceWitness::random_blinding(&mut rng),
};
let txs = vec![(signed_withdraw, ptx.input_witness(0))];
let proof = prove_zone_stf(
zone_start.state.clone(),
txs,
ptx.input_witness(1),
ptx.output_witness(0),
ptx.output_witness(1),
);
assert!(proof.verify(ConstraintPublic {
nf: zone_start.state_input_witness().nullifier(),
ptx_root: ptx.commit().root(),
}))
}
#[test]
fn test_prove_zone_fund_constraint() {
let mut rng = rand::thread_rng();
let zone = ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([]), &mut rng);
let ptx = PartialTxWitness {
inputs: vec![zone.fund_input_witness()],
outputs: vec![zone.state_note],
balance_blinding: BalanceWitness::random_blinding(&mut rng),
};
let proof =
prove_zone_fund_constraint(ptx.input_witness(0), ptx.output_witness(0), &zone.state);
assert!(proof.verify(ConstraintPublic {
nf: zone.fund_input_witness().nullifier(),
ptx_root: ptx.commit().root(),
}))
}
#[test]
fn test_prove_user_atomic_transfer() {
let mut rng = rand::thread_rng();
let alice = common::new_account(&mut rng);
let alice_vk = alice.verifying_key().to_bytes();
let zone_a =
ZoneNotes::new_with_balances("ZONE_A", BTreeMap::from_iter([(alice_vk, 40)]), &mut rng);
let zone_b = ZoneNotes::new_with_balances("ZONE_B", BTreeMap::new(), &mut rng);
let user_intent = UserIntent {
zone_a_meta: zone_a.state.zone_metadata,
zone_b_meta: zone_b.state.zone_metadata,
withdraw: Withdraw {
from: alice_vk,
amount: 32,
},
deposit: Deposit {
to: alice_vk,
amount: 32,
},
};
let user_note = cl::InputWitness::public(cl::OutputWitness::public(NoteWitness {
value: 1,
unit: derive_unit("INTENT"),
constraint: ConstraintProof::nop_constraint(),
state: user_intent.commit(),
nonce: Nonce::random(&mut rng),
}));
let (zone_a, withdraw_included_witnesss) = zone_a.run(Tx::Withdraw(user_intent.withdraw));
let (zone_b, deposit_included_witnesss) = zone_b.run(Tx::Deposit(user_intent.deposit));
let ptx = PartialTxWitness {
inputs: vec![user_note],
outputs: vec![zone_a.state_note, zone_b.state_note],
balance_blinding: BalanceWitness::random_blinding(&mut rng),
};
let user_atomic_transfer = UserAtomicTransfer {
user_note: ptx.input_witness(0),
user_intent,
zone_a: ptx.output_witness(0),
zone_b: ptx.output_witness(1),
zone_a_roots: zone_a.state.state_roots(),
zone_b_roots: zone_b.state.state_roots(),
withdraw_tx: withdraw_included_witnesss,
deposit_tx: deposit_included_witnesss,
};
let proof = prove_user_atomic_transfer(user_atomic_transfer);
assert!(proof.verify(ConstraintPublic {
nf: user_note.nullifier(),
ptx_root: ptx.commit().root(),
}))
}
}