Finish implementation of the stf

This commit is contained in:
Giacomo Pasini 2025-02-27 13:53:07 +01:00
parent 447efee968
commit ce6569b7a5
No known key found for this signature in database
GPG Key ID: FC08489D2D895D4B
12 changed files with 170 additions and 129 deletions

View File

@ -1,5 +1,11 @@
[workspace]
resolver = "2"
members = ["app"]
members = ["app", "stf/host", "stf/methods"]
# Always optimize; building and running the guest takes much longer without optimization.
[profile.dev]
opt-level = 3
[profile.release]
debug = 1
lto = true

View File

@ -5,4 +5,5 @@ edition = "2021"
[dependencies]
cl = { path = "../../../cl/cl" }
risc0-zkvm = "1.2"
risc0-zkvm = "1.2"
serde = { version = "1.0", features = ["derive"] }

View File

@ -1,25 +1,14 @@
use cl::{
crust::{
balance::{UnitWitness, NOP_COVENANT},
Nullifier, Tx, Unit,
},
mantle::{ledger::Ledger, ZoneId, ZoneState},
crust::{InputWitness, Nullifier, NullifierSecret, Tx, Unit},
mantle::ZoneId,
};
use risc0_zkvm::sha::rust_crypto::{Digest, Sha256};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
const SWAP_GOAL_UNIT: UnitWitness = UnitWitness {
spending_covenant: NOP_COVENANT,
minting_covenant: NOP_COVENANT,
burning_covenant: NOP_COVENANT,
};
pub struct SwapVmPrivate {
pub old: ZoneState,
pub new_ledger: Ledger,
pub data: ZoneData,
}
const FUNDS_SK: NullifierSecret = NullifierSecret([0; 16]);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Swap {
pair: Pair,
t0_in: u64,
@ -28,36 +17,42 @@ pub struct Swap {
t1_out: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddLiquidity {
pair: Pair,
t0_in: u64,
t1_in: u64,
_pair: Pair,
_t0_in: u64,
_t1_in: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoveLiquidity {
shares: Unit,
_shares: Unit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZoneData {
nfs: BTreeSet<Nullifier>,
pools: BTreeMap<Pair, Pool>,
zone_id: ZoneId,
pub nfs: BTreeSet<Nullifier>,
pub pools: BTreeMap<Pair, Pool>,
pub zone_id: ZoneId,
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)]
pub struct Pair {
pub t0: Unit,
pub t1: Unit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pool {
pub balance_0: u64,
pub balance_1: u64,
}
/// Prove the data was part of the tx output
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputDataProof;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ZoneOp {
Swap {
tx: Tx,
@ -97,7 +92,7 @@ impl ZoneData {
let balance_1_final = balance_1_start + swap.t1_in as u128 - swap.t1_out as u128;
(balance_0_final * 1000 - 3 * swap.t0_in as u128)
* (balance_0_final * 1000 - 3 * swap.t1_in as u128)
* (balance_1_final * 1000 - 3 * swap.t1_in as u128)
== balance_0_start * balance_1_start
}
@ -112,9 +107,7 @@ impl ZoneData {
pub fn validate_op(&self, op: &ZoneOp) -> bool {
match op {
ZoneOp::Swap { tx, swap, proof } => {
self.check_swap(&swap) && self.validate_no_pools(&tx)
}
ZoneOp::Swap { tx, swap, .. } => self.check_swap(&swap) && self.validate_no_pools(&tx), // TODO: check proof
ZoneOp::AddLiquidity { tx, .. } => self.validate_no_pools(&tx),
ZoneOp::RemoveLiquidity { tx, .. } => self.validate_no_pools(&tx), // should we check shares exist?
ZoneOp::Ledger(tx) => {
@ -125,6 +118,37 @@ impl ZoneData {
}
}
pub fn pools_update(&mut self, tx: &Tx, notes: &[InputWitness]) {
// check all previous nullifiers are used
assert!(self.nfs.iter().all(|nf| tx
.updates
.iter()
.filter(|u| u.zone_id == self.zone_id)
.flat_map(|u| u.inputs.iter())
.find(|nf2| *nf2 == nf)
.is_some()));
self.nfs.clear();
// check the exepected pool balances are reflected in the tx outputs
let outputs = tx
.updates
.iter()
.filter(|u| u.zone_id == self.zone_id)
.flat_map(|u| u.outputs.iter())
.collect::<Vec<_>>();
let expected_pool_balances = self.expected_pool_balances();
for note in notes {
assert_eq!(note.nf_sk, FUNDS_SK);
// TODO: check nonce derivation
let output = note.to_output();
let value = expected_pool_balances.get(&output.unit).unwrap();
assert_eq!(note.value, *value);
assert!(outputs.contains(&&output.note_commitment()));
self.nfs.insert(note.nullifier());
}
}
pub fn expected_pool_balances(&self) -> BTreeMap<Unit, u64> {
let mut expected_pool_balances = BTreeMap::new();
for (Pair { t0, t1 }, pool) in self.pools.iter() {
@ -137,22 +161,15 @@ impl ZoneData {
pub fn process_op(&mut self, op: &ZoneOp) {
match op {
ZoneOp::Swap { tx, swap, proof } => {
ZoneOp::Swap { tx, swap, .. } => {
self.swap(&swap);
self.validate_no_pools(&tx);
// TODO: check the proof
}
ZoneOp::AddLiquidity {
tx,
add_liquidity,
proof,
} => {
ZoneOp::AddLiquidity { .. } => {
todo!()
}
ZoneOp::RemoveLiquidity {
tx,
remove_liquidity,
proof,
} => {
ZoneOp::RemoveLiquidity { .. } => {
todo!()
}
ZoneOp::Ledger(tx) => {
@ -163,6 +180,10 @@ impl ZoneData {
}
}
pub fn update_and_commit(self) -> ZoneData {
self
}
pub fn commit(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
for nf in &self.nfs {

View File

@ -1,14 +0,0 @@
[workspace]
resolver = "2"
members = ["host", "methods"]
[dependencies]
app = { path = "../app" }
# Always optimize; building and running the guest takes much longer without optimization.
[profile.dev]
opt-level = 3
[profile.release]
debug = 1
lto = true

View File

@ -8,3 +8,6 @@ methods = { path = "../methods" }
risc0-zkvm = { version = "1.2.0" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
serde = "1.0"
ledger_proof_statements = { path = "../../../../cl/ledger_proof_statements" }
app = { path = "../../app" }
cl = { path = "../../../../cl/cl" }

View File

@ -0,0 +1,32 @@
// These constants represent the RISC-V ELF and the image ID generated by risc0-build.
// The ELF is used for proving and the ID is used for verification.
use app::ZoneOp;
use cl::mantle::{ledger::Ledger, zone::ZoneData};
use ledger_proof_statements::ledger::SyncLog;
use methods::{STF_ELF, STF_ID};
use risc0_zkvm::{ExecutorEnv, Prover, Receipt, Result};
pub struct StfPrivate {
pub zone_data: ZoneData,
pub old_ledger: Ledger,
pub new_ledger: Ledger,
pub sync_logs: Vec<SyncLog>,
pub ops: Vec<ZoneOp>,
}
impl StfPrivate {
pub fn prove(&self, prover: &impl Prover) -> Result<Receipt> {
let env = ExecutorEnv::builder()
.write(&self.zone_data)?
.write(&self.old_ledger)?
.write(&self.new_ledger)?
.write(&self.sync_logs)?
.write(&STF_ID)?
.build()?;
let prove_info = prover.prove(env, STF_ELF)?;
debug_assert!(prove_info.receipt.verify(STF_ID).is_ok());
Ok(prove_info.receipt)
}
}

View File

@ -1,56 +0,0 @@
// These constants represent the RISC-V ELF and the image ID generated by risc0-build.
// The ELF is used for proving and the ID is used for verification.
use methods::{
STF_ELF, STF_ID
};
use risc0_zkvm::{default_prover, ExecutorEnv};
fn main() {
// Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run`
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env())
.init();
// An executor environment describes the configurations for the zkVM
// including program inputs.
// A default ExecutorEnv can be created like so:
// `let env = ExecutorEnv::builder().build().unwrap();`
// However, this `env` does not have any inputs.
//
// To add guest input to the executor environment, use
// ExecutorEnvBuilder::write().
// To access this method, you'll need to use ExecutorEnv::builder(), which
// creates an ExecutorEnvBuilder. When you're done adding input, call
// ExecutorEnvBuilder::build().
// For example:
let input: u32 = 15 * u32::pow(2, 27) + 1;
let env = ExecutorEnv::builder()
.write(&input)
.unwrap()
.build()
.unwrap();
// Obtain the default prover.
let prover = default_prover();
// Proof information by proving the specified ELF binary.
// This struct contains the receipt along with statistics about execution of the guest
let prove_info = prover
.prove(env, STF_ELF)
.unwrap();
// extract the receipt.
let receipt = prove_info.receipt;
// TODO: Implement code for retrieving receipt journal here.
// For example:
let _output: u32 = receipt.journal.decode().unwrap();
// The receipt was verified at the end of proving, but the below code is an
// example of how someone else could verify this receipt.
receipt
.verify(STF_ID)
.unwrap();
}

View File

@ -3,6 +3,9 @@ name = "methods"
version = "0.1.0"
edition = "2021"
[dependencies]
app = { path = "../../app" }
[build-dependencies]
risc0-build = { version = "1.2.0" }

View File

@ -7,3 +7,7 @@ edition = "2021"
[dependencies]
risc0-zkvm = { version = "1.2.0", default-features = false, features = ['std'] }
app = { path = "../../../app" }
ledger_proof_statements = { path = "../../../../../cl/ledger_proof_statements" }
cl = { path = "../../../../../cl/cl" }
ledger_validity_proof = { path = "../../../../../cl/ledger_validity_proof" }

View File

@ -1,50 +1,80 @@
use app::{ZoneData, ZoneOp};
use cl::{
crust::Tx,
mantle::{ledger::Ledger, zone::ZoneState},
};
use ledger_proof_statements::{
ledger::{LedgerProofPublic, SyncLog},
stf::StfPublic,
};
use risc0_zkvm::guest::env;
fn main() {
let mut inputs: SwapVmPrivate = env::read();
let mut zone_data: ZoneData = env::read();
let old_ledger: Ledger = env::read();
let ledger: Ledger = env::read();
let sync_logs: Vec<SyncLog> = env::read();
let stf: [u8; 32] = env::read();
let ops: Vec<ZoneOp> = env::read();
let zone_id = inputs.zone_data.zone_id;
let zone_id = zone_data.zone_id;
assert_eq!(inputs.zone_data.commit(), inputs.old.zone_data);
let old_zone_data = zone_data.commit();
for op in ops {
zone_data.process_op(tx);
for op in &ops {
zone_data.process_op(op);
}
let txs = ops
let txs: Vec<&Tx> = ops
.iter()
.map(|op| match op {
ZoneOp::Swap { tx, swap, proof } => tx,
ZoneOp::Swap { tx, .. } => tx,
ZoneOp::AddLiquidity { tx, .. } => tx,
ZoneOp::RemoveLiquidity { tx, .. } => tx,
ZoneOp::Ledger(tx) => tx,
})
.collect();
let sync_logs = vec![]; // get this from outside
let outputs = txs
.iter()
.flat_map(|tx| tx.outputs.clone())
.filter(|o| o.zone_id == zone_id)
.flat_map(|tx| tx.updates.iter().filter(|u| u.zone_id == zone_id))
.flat_map(|u| u.outputs.iter())
.copied()
.collect();
let inputs = txs
// TODO: inputs missings from ledger proof public
let _inputs: Vec<_> = txs
.iter()
.flat_map(|tx| tx.inputs.clone())
.filter(|i| i.zone_id == zone_id)
.flat_map(|tx| tx.updates.iter().filter(|u| u.zone_id == zone_id))
.flat_map(|u| u.inputs.iter())
.copied()
.collect();
let ledger_public = LedgerProofPublic {
old_ledger: inputs.old.ledger,
ledger: inputs.new_ledger,
old_ledger,
ledger,
id: zone_id,
sync_log,
sync_logs,
outputs,
};
env::verify(
ledger_validity_proof::LEDGER_ID,
&serde::to_vec(&ledger_public).unwrap(),
&risc0_zkvm::serde::to_vec(&ledger_public).unwrap(),
)
.unwrap();
let public = StfPublic {
old: ZoneState {
ledger: old_ledger,
zone_data: old_zone_data,
stf,
},
new: ZoneState {
ledger,
zone_data: zone_data.commit(),
stf,
},
};
env::commit(&public);
}

View File

@ -73,6 +73,17 @@ impl InputWitness {
self.nf_sk.commit(),
)
}
pub fn to_output(&self) -> OutputWitness {
OutputWitness {
state: self.state,
value: self.value,
unit: self.unit_witness.unit(),
nonce: self.nonce,
zone_id: self.zone_id,
nf_pk: self.nf_sk.commit(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]