Merge pull request #54 from logos-co/drusu/add-swap-vm

SwapVM
This commit is contained in:
davidrusu 2025-03-17 18:46:20 +04:00 committed by GitHub
commit 19c66f31c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 997 additions and 746 deletions

View File

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

View File

@ -1,23 +1,91 @@
use cl::{
crust::{
balance::{UnitWitness, NOP_COVENANT},
tx::LedgerUpdate,
InputWitness, Nonce, Nullifier, NullifierCommitment, NullifierSecret, OutputWitness, Tx,
Unit,
},
mantle::ZoneId,
};
use rand::RngCore;
use risc0_zkvm::sha::rust_crypto::{Digest, Sha256};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
const FUNDS_SK: NullifierSecret = NullifierSecret([0; 16]);
pub const ZONE_ID: [u8; 32] = [128; 32];
pub fn swap_goal_unit() -> UnitWitness {
UnitWitness::nop(b"SWAP")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct SwapOutput {
// value will be set at the market price
pub state: [u8; 32],
pub unit: Unit,
pub nonce: Nonce,
pub zone_id: ZoneId,
pub nf_pk: NullifierCommitment,
}
impl SwapOutput {
pub fn basic(
unit: Unit,
zone_id: ZoneId,
nf_pk: NullifierCommitment,
rng: impl RngCore,
) -> Self {
Self {
state: [0; 32],
unit,
nonce: Nonce::random(rng),
zone_id,
nf_pk,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SwapArgs {
// the user specifies the template forthe output note
pub output: SwapOutput,
// minimum value of the output note
pub limit: u64,
// the nonce used in the swap goal note
pub nonce: Nonce,
}
impl SwapArgs {
pub fn to_output(self, value: u64) -> OutputWitness {
assert!(value >= self.limit);
OutputWitness {
state: self.output.state,
value,
unit: self.output.unit,
nonce: self.output.nonce,
zone_id: self.output.zone_id,
nf_pk: self.output.nf_pk,
}
}
}
pub fn swap_goal_note(nonce: Nonce) -> InputWitness {
InputWitness {
state: [0u8; 32],
value: 1,
unit_witness: swap_goal_unit(),
nonce,
zone_id: ZONE_ID,
nf_sk: NullifierSecret::zero(),
}
}
// TODO: order pair tokens lexicographically
fn get_pair_share_unit(pair: Pair) -> UnitWitness {
let mut hasher = Sha256::new();
hasher.update(b"SWAP_PAIR_SHARE_UNIT");
hasher.update(&pair.t0);
hasher.update(&pair.t1);
hasher.update(pair.t0);
hasher.update(pair.t1);
UnitWitness {
spending_covenant: NOP_COVENANT,
minting_covenant: NOP_COVENANT,
@ -35,63 +103,12 @@ pub struct Swap {
t1_out: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddLiquidity {
pub pair: Pair,
pub t0_in: u64,
pub t1_in: u64,
pub pk_out: NullifierCommitment,
pub nonce: Nonce,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharesToMint {
pub amount: u64,
pub unit: Unit,
pub pk_out: NullifierCommitment,
pub nonce: Nonce,
}
impl SharesToMint {
pub fn to_output(&self, zone_id: ZoneId) -> OutputWitness {
OutputWitness {
state: [0; 32],
value: self.amount,
unit: self.unit,
nonce: self.nonce,
zone_id,
nf_pk: self.pk_out,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoveLiquidity {
shares: InputWitness,
nf_pk: NullifierCommitment,
nonce: Nonce,
}
impl RemoveLiquidity {
pub fn to_output(&self, value: u64, unit: Unit, zone_id: ZoneId) -> OutputWitness {
OutputWitness {
state: [0; 32],
value,
unit,
nonce: self.nonce,
zone_id,
nf_pk: self.nf_pk,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZoneData {
pub nfs: BTreeSet<Nullifier>,
pub pools: BTreeMap<Pair, Pool>,
pub zone_id: ZoneId,
pub shares_to_mint: Vec<SharesToMint>,
pub shares_to_redeem: Vec<OutputWitness>,
pub swaps_output: Vec<OutputWitness>,
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)]
@ -100,6 +117,15 @@ pub struct Pair {
pub t1: Unit,
}
impl Pair {
pub fn new(t_a: Unit, t_b: Unit) -> Self {
Self {
t0: std::cmp::min(t_a, t_b),
t1: std::cmp::max(t_a, t_b),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pool {
pub balance_0: u64,
@ -108,102 +134,87 @@ pub struct Pool {
pub total_shares: 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(Swap),
AddLiquidity {
tx: Tx,
add_liquidity: AddLiquidity,
proof: OutputDataProof,
},
RemoveLiquidity {
tx: Tx,
remove_liquidity: RemoveLiquidity,
proof: OutputDataProof,
},
Ledger(Tx),
}
/// This contains the changes at the ledger level that can only be done by the executor
/// because they are dependant on the order of transactions.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateUpdate {
pub tx: Tx,
/// Update the balance of the pool
pub pool_notes: Vec<InputWitness>,
impl Pool {
pub fn price(&self) -> f64 {
self.balance_1 as f64 / self.balance_0 as f64
}
}
// Txs are of the following form:
impl ZoneData {
/// A swap does not need to directly modify the pool balances, but the executor
/// should make sure that required funds are provided.
pub fn swap(&mut self, swap: &Swap) {
assert!(self.check_swap(swap));
let pool = self.pools.get_mut(&swap.pair).unwrap();
pool.balance_0 += swap.t0_in - swap.t0_out;
pool.balance_1 += swap.t1_in - swap.t1_out;
}
pub fn check_swap(&self, swap: &Swap) -> bool {
let Some(pool) = self.pools.get(&swap.pair) else {
return false;
};
let balance_0_start = pool.balance_0 as u128;
let balance_1_start = pool.balance_1 as u128;
let balance_0_final = balance_0_start + swap.t0_in as u128 - swap.t0_out as u128;
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_1_final * 1000 - 3 * swap.t1_in as u128)
== balance_0_start * balance_1_start
}
/// Check no pool notes are used in this tx
pub fn validate_no_pools(&self, tx: &Tx) -> bool {
tx.updates
.iter()
.filter(|u| u.zone_id == self.zone_id)
.flat_map(|u| u.inputs.iter())
.all(|nf| !self.nfs.contains(nf))
}
pub fn validate_op(&self, op: &ZoneOp) -> bool {
match op {
ZoneOp::Swap(swap) => self.check_swap(&swap),
ZoneOp::AddLiquidity { tx, .. } => self.validate_no_pools(&tx),
ZoneOp::RemoveLiquidity { tx, .. } => self.validate_no_pools(&tx), // should we check shares exist?
ZoneOp::Ledger(tx) => {
// Just a ledger tx that does not directly interact with the zone,
// just validate it's not using pool notes
self.validate_no_pools(tx)
}
pub fn new() -> Self {
Self {
nfs: Default::default(),
pools: Default::default(),
zone_id: ZONE_ID,
swaps_output: Default::default(),
}
}
pub fn pair_price(&self, t_in: Unit, t_out: Unit) -> Option<f64> {
let pair = Pair::new(t_in, t_out);
self.pools.get(&pair).map(|pool| pool.price()).map(|price| {
if t_in == pair.t0 {
price
} else {
1.0 / price
}
})
}
pub fn amount_out(&self, t_in: Unit, t_out: Unit, amount_in: u64) -> Option<u64> {
let pair = Pair::new(t_in, t_out);
let pool = self.pools.get(&pair)?;
let (balance_in, balance_out) = if pair.t0 == t_in {
(pool.balance_0, pool.balance_1)
} else {
(pool.balance_1, pool.balance_0)
};
let amount_in_after_fee = amount_in * 1000 - amount_in * 3;
let amount_out =
(balance_out * 1000 * amount_in_after_fee) / (balance_in * 1000 + amount_in_after_fee);
Some(amount_out / 1000)
}
/// A swap does not need to directly modify the pool balances, but the executor
/// should make sure that required funds are provided.
pub fn swap(&mut self, t_in: Unit, amount_in: u64, swap: SwapArgs) {
// TODO: calculate amout outside proof and check here for efficiency
let amount_out = self.amount_out(t_in, swap.output.unit, amount_in).unwrap();
let pair = Pair::new(t_in, swap.output.unit);
let pool = self.pools.get_mut(&pair).unwrap();
let (balance_in, balance_out) = if pair.t0 == t_in {
(&mut pool.balance_0, &mut pool.balance_1)
} else {
(&mut pool.balance_1, &mut pool.balance_0)
};
*balance_in += amount_in;
*balance_out -= amount_out;
self.swaps_output.push(swap.to_output(amount_out));
}
/// Check no pool notes are used in this tx
pub fn validate_no_pools(&self, zone_update: &LedgerUpdate) -> bool {
self.nfs.iter().all(|nf| !zone_update.has_input(nf))
}
pub fn pools_update(&mut self, tx: &Tx, pool_notes: &[InputWitness]) {
let Some(zone_update) = tx.updates.get(&self.zone_id) else {
// The tx is not involving this zone, nothing to do.
return;
};
// 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()));
assert!(self.nfs.iter().all(|nf| zone_update.has_input(nf)));
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 pool_notes {
assert_eq!(note.nf_sk, FUNDS_SK);
@ -211,40 +222,12 @@ impl ZoneData {
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()));
assert!(zone_update.has_output(&output.note_commitment()));
self.nfs.insert(note.nullifier());
}
}
pub fn check_minted_shares(&self, tx: &Tx) {
let outputs = tx
.updates
.iter()
.filter(|u| u.zone_id == self.zone_id)
.flat_map(|u| u.outputs.iter())
.collect::<Vec<_>>();
for shares in &self.shares_to_mint {
let output = shares.to_output(self.zone_id);
assert!(outputs.contains(&output.note_commitment()));
}
}
pub fn check_redeemed_shares(&self, tx: &Tx) {
let outputs = tx
.updates
.iter()
.filter(|u| u.zone_id == self.zone_id)
.flat_map(|u| u.outputs.iter())
.collect::<Vec<_>>();
for shares in &self.shares_to_redeem {
assert!(outputs.contains(&shares.note_commitment()));
}
// TODO: chech shares have been burned
}
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() {
@ -255,78 +238,35 @@ impl ZoneData {
expected_pool_balances
}
pub fn add_liquidity(&mut self, add_liquidity: &AddLiquidity) {
let pool = self.pools.entry(add_liquidity.pair).or_insert(Pool {
balance_0: add_liquidity.t0_in,
balance_1: add_liquidity.t1_in,
shares_unit: get_pair_share_unit(add_liquidity.pair).unit(),
total_shares: 1,
// TODO: only for testing purposes
pub fn add_liquidity(&mut self, t0_unit: Unit, t1_unit: Unit, t0_in: u64, t1_in: u64) {
let pair = Pair::new(t0_unit, t1_unit);
let pool = self.pools.entry(pair).or_insert(Pool {
balance_0: 0,
balance_1: 0,
shares_unit: get_pair_share_unit(pair).unit(),
total_shares: 0,
});
let minted_shares = (add_liquidity.t0_in * pool.total_shares / pool.balance_0)
.min(add_liquidity.t1_in * pool.total_shares / pool.balance_1);
pool.total_shares += minted_shares; // fix for first deposit
pool.balance_0 += add_liquidity.t0_in;
pool.balance_1 += add_liquidity.t1_in;
let (balance_0, balance_1) = if pair.t0 == t0_unit {
(&mut pool.balance_0, &mut pool.balance_1)
} else {
(&mut pool.balance_1, &mut pool.balance_0)
};
self.shares_to_mint.push(SharesToMint {
amount: minted_shares,
unit: pool.shares_unit,
pk_out: add_liquidity.pk_out,
nonce: add_liquidity.nonce,
});
*balance_0 += t0_in;
*balance_1 += t1_in;
}
pub fn remove_liquidity(&mut self, remove_liquidity: &RemoveLiquidity) {
let shares = remove_liquidity.shares;
let (pair, pool) = self
.pools
.iter_mut()
.find(|(_, pool)| pool.shares_unit == shares.unit_witness.unit())
.unwrap();
let t0_out = pool.balance_0 * shares.value / pool.total_shares;
let t1_out = pool.balance_1 * shares.value / pool.total_shares;
pool.balance_0 -= t0_out;
pool.balance_1 -= t1_out;
pool.total_shares -= shares.value;
self.shares_to_redeem
.push(remove_liquidity.to_output(t0_out, pair.t0, self.zone_id));
self.shares_to_redeem
.push(remove_liquidity.to_output(t1_out, pair.t1, self.zone_id));
}
pub fn process_op(&mut self, op: &ZoneOp) {
match op {
ZoneOp::Swap(swap) => self.swap(&swap),
ZoneOp::AddLiquidity {
tx, add_liquidity, ..
} => {
self.add_liquidity(&add_liquidity);
assert!(self.validate_no_pools(&tx));
// TODo: check proof
}
ZoneOp::RemoveLiquidity {
tx,
remove_liquidity,
..
} => {
self.remove_liquidity(&remove_liquidity);
assert!(self.validate_no_pools(&tx));
// TODO: check proof
}
ZoneOp::Ledger(tx) => {
// Just a ledger tx that does not directly interact with the zone,
// just validate it's not using pool notes
self.validate_no_pools(tx);
}
pub fn check_swaps_executed(&self, tx: &Tx) {
let zone_update = tx.updates.get(&self.zone_id).unwrap();
for output in &self.swaps_output {
assert!(zone_update.has_output(&output.note_commitment()));
}
}
pub fn update_and_commit(mut self, updates: &StateUpdate) -> [u8; 32] {
self.pools_update(&updates.tx, &updates.pool_notes);
self.check_minted_shares(&updates.tx);
self.check_redeemed_shares(&updates.tx);
pub fn update_and_commit(mut self, tx: &Tx, pool_notes: &[InputWitness]) -> [u8; 32] {
self.pools_update(&tx, pool_notes);
self.check_swaps_executed(&tx);
self.commit()
}
@ -336,12 +276,18 @@ impl ZoneData {
hasher.update(nf);
}
for (pair, pool) in self.pools.iter() {
hasher.update(&pair.t0);
hasher.update(&pair.t1);
hasher.update(&pool.balance_0.to_le_bytes());
hasher.update(&pool.balance_1.to_le_bytes());
hasher.update(pair.t0);
hasher.update(pair.t1);
hasher.update(pool.balance_0.to_le_bytes());
hasher.update(pool.balance_1.to_le_bytes());
}
hasher.update(&self.zone_id);
hasher.update(self.zone_id);
hasher.finalize().into()
}
}
impl Default for ZoneData {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,45 @@
use app::ZoneData;
use cl::crust::UnitWitness;
fn nmo() -> UnitWitness {
UnitWitness::nop(b"NMO")
}
fn mem() -> UnitWitness {
UnitWitness::nop(b"MEM")
}
#[test]
fn pair_price() {
let mut swapvm_state = ZoneData::new();
// initially there is no NMO/MEM pair
assert_eq!(swapvm_state.pair_price(nmo().unit(), mem().unit()), None);
swapvm_state.add_liquidity(nmo().unit(), mem().unit(), 10, 100);
// given that there is 1nmo:10mem in the pool, the price should show that we get 10 MEM for 1 NMO
assert_eq!(
swapvm_state.pair_price(nmo().unit(), mem().unit()),
Some(10.0)
);
// switching the trade direction should flip the price as well
assert_eq!(
swapvm_state.pair_price(mem().unit(), nmo().unit()),
Some(0.1)
);
// Due to slippage, the amount we get out is less than what the price would imply
assert_eq!(
swapvm_state.amount_out(nmo().unit(), mem().unit(), 1),
Some(9) // 1 MEM slippage
);
assert_eq!(
swapvm_state.amount_out(nmo().unit(), mem().unit(), 2),
Some(16) // 4 MEM slippage
);
assert_eq!(
swapvm_state.amount_out(nmo().unit(), mem().unit(), 5),
Some(33) // 17 MEM slippage
);
}

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8"
methods = { path = "../methods" }
risc0-zkvm = { version = "1.2.0" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
@ -11,3 +12,4 @@ serde = "1.0"
ledger_proof_statements = { path = "../../../../cl/ledger_proof_statements" }
app = { path = "../../app" }
cl = { path = "../../../../cl/cl" }
ledger = { path = "../../../../cl/ledger" }

View File

@ -1,33 +1,196 @@
use app::{StateUpdate, ZoneOp};
use cl::mantle::{ledger::Ledger, zone::ZoneData};
use ledger_proof_statements::ledger::SyncLog;
use std::collections::BTreeMap;
use app::{swap_goal_unit, SwapArgs, ZoneData};
use cl::crust::{BundleWitness, InputWitness, NoteCommitment, Nullifier, Tx, TxWitness, Unit};
use cl::ds::mmr::{MMRFolds, MMRProof, MMR};
use cl::mantle::ledger::{Ledger, LedgerState, LedgerWitness};
use cl::mantle::ZoneState;
use ledger::stf::{risc0_stf, StfProof};
use methods::{STF_ELF, STF_ID};
use risc0_zkvm::{ExecutorEnv, Prover, Receipt, Result};
use risc0_zkvm::{ExecutorEnv, Prover, Result};
#[derive(Debug)]
struct FundNote {
note: InputWitness,
mmr: MMR,
path: MMRProof,
}
impl FundNote {
fn evolve(&self, new_amount: u64) -> InputWitness {
let mut new_note = self.note.clone();
new_note.value = new_amount;
new_note.nonce = new_note.evolved_nonce(b"SWAP");
new_note
}
}
#[derive(Debug, Default)]
pub struct ExecutorState {
pub ledger: LedgerState,
pub swapvm: ZoneData,
fund_notes: BTreeMap<Unit, FundNote>,
goal_notes: Vec<(InputWitness, MMR, MMRProof)>,
}
impl ExecutorState {
pub fn zone_state(&self) -> ZoneState {
ZoneState {
stf: risc0_stf(STF_ID),
zone_data: self.swapvm.commit(),
ledger: self.ledger.to_witness().commit(),
}
}
pub fn observe_cm(&mut self, cm: &NoteCommitment) -> ((MMR, MMRProof), MMRFolds) {
let folds = self.ledger.commitments.folds(&cm.0);
for (_, fund_note) in self.fund_notes.iter_mut() {
assert_eq!(fund_note.mmr, self.ledger.commitments);
fund_note
.path
.update(&fund_note.note.note_commitment().0, &folds);
}
let proof = self.ledger.add_commitment(cm);
for (_, fund_note) in self.fund_notes.iter_mut() {
fund_note.mmr = self.ledger.commitments.clone();
}
(proof, folds)
}
pub fn observe_nfs(&mut self, nfs: Vec<Nullifier>) {
self.ledger.add_nullifiers(nfs);
}
pub fn process_tx(&mut self, tx: &Tx) {
let Some(swapvm_update) = tx.updates.get(&self.swapvm.zone_id) else {
// this tx is not related to the swapvm zone
return;
};
let mut output_mmr_proofs = BTreeMap::<NoteCommitment, MMRProof>::new();
for (cm, _) in &swapvm_update.outputs {
let (proof, folds) = self.observe_cm(cm);
for (other_cm, other_cm_proof) in output_mmr_proofs.iter_mut() {
other_cm_proof.update(&other_cm.0, &folds);
}
output_mmr_proofs.insert(*cm, proof.1);
for (other_cm, other_cm_proof) in &output_mmr_proofs {
assert!(self
.ledger
.commitments
.verify_proof(&other_cm.0, &other_cm_proof))
}
}
if tx.balance.unit_balance(swap_goal_unit().unit()).is_neg() {
// this is a SWAP
let (swap_goal_cm, swap_args_bytes) = &swapvm_update.outputs[0];
let swap_args: SwapArgs = cl::deserialize(&swap_args_bytes);
// verify the user proved the correct swap goal note
let swap_goal_witness = app::swap_goal_note(swap_args.nonce);
assert_eq!(swap_goal_cm, &swap_goal_witness.note_commitment());
self.goal_notes.push((
swap_goal_witness,
self.ledger.commitments.clone(),
output_mmr_proofs[swap_goal_cm].clone(),
));
// assume there are only the goal unit and tokenIn units at play
assert_eq!(tx.balance.balances.len(), 2);
let balance_in = tx
.balance
.balances
.iter()
.find(|bal| bal.unit != swap_goal_unit().unit())
.unwrap();
let token_in = balance_in.unit;
assert_eq!(balance_in.neg, 0);
assert!(balance_in.pos > 0);
let amount_in = balance_in.pos;
self.swapvm.swap(token_in, amount_in, swap_args);
}
}
pub fn update_and_get_executor_tx(&mut self) -> (TxWitness, Vec<InputWitness>) {
let mut tx = TxWitness::default();
let mut new_fund_notes = Vec::new();
let expected_pool_balances = self.swapvm.expected_pool_balances();
let fund_notes = std::mem::take(&mut self.fund_notes);
for note in &self.swapvm.swaps_output {
tx = tx.add_output(note.clone(), "");
self.ledger.add_commitment(&note.note_commitment());
}
self.swapvm.nfs.clear();
for (unit, value) in expected_pool_balances {
let note = if let Some(note) = fund_notes.get(&unit) {
note.evolve(value)
} else {
panic!("dynamically created fund notes are not supported");
};
new_fund_notes.push(note);
let output = note.to_output();
tx = tx.add_output(output, "");
let (mmr, path) = self.ledger.add_commitment(&output.note_commitment());
self.fund_notes
.insert(note.unit_witness.unit(), FundNote { note, mmr, path });
self.swapvm.nfs.insert(note.nullifier());
}
for (_, FundNote { note, mmr, path }) in fund_notes.into_iter() {
tx = tx.add_input(note, (mmr, path));
}
for (goal_note, mmr, path) in std::mem::take(&mut self.goal_notes) {
tx = tx.add_input(goal_note, (mmr, path));
}
(tx, new_fund_notes)
}
pub fn set_fund_note(&mut self, note: InputWitness, mmr: MMR, path: MMRProof) {
self.fund_notes
.insert(note.unit_witness.unit(), FundNote { note, mmr, path });
}
}
pub struct StfPrivate {
pub zone_data: ZoneData,
pub old_ledger: Ledger,
pub old_ledger: LedgerWitness,
pub new_ledger: Ledger,
pub sync_logs: Vec<SyncLog>,
pub ops: Vec<ZoneOp>,
pub update_tx: StateUpdate,
pub fund_notes: Vec<InputWitness>,
pub bundle: BundleWitness,
}
impl StfPrivate {
pub fn prove(&self, prover: &impl Prover) -> Result<Receipt> {
pub fn prove(&self, prover: &dyn Prover) -> Result<StfProof> {
let env = ExecutorEnv::builder()
.write(&self.zone_data)?
.write(&self.old_ledger)?
.write(&self.new_ledger)?
.write(&self.sync_logs)?
.write(&STF_ID)?
.write(&self.ops)?
.write(&self.update_tx)?
.write(&unsafe { std::intrinsics::transmute::<_, [u8; 32]>(STF_ID) })?
.write(&self.bundle)?
.write(&self.fund_notes)?
.build()?;
let prove_info = prover.prove(env, STF_ELF)?;
debug_assert!(prove_info.receipt.verify(STF_ID).is_ok());
Ok(prove_info.receipt)
Ok(StfProof::from_risc0(risc0_stf(STF_ID), prove_info.receipt))
}
}

View File

@ -0,0 +1,156 @@
use app::ZONE_ID;
use cl::crust::{BundleWitness, InputWitness, Nonce, NullifierSecret, TxWitness, UnitWitness};
use cl::mantle::ledger::LedgerState;
use cl::mantle::update::{BatchUpdate, Update};
use host::{ExecutorState, StfPrivate};
use ledger::ledger::ProvedLedgerTransition;
use ledger::update::ProvedBatchUpdate;
use ledger::{bundle::ProvedBundle, tx::ProvedTx};
use rand::RngCore;
fn nmo() -> UnitWitness {
UnitWitness::nop(b"NMO")
}
fn mem() -> UnitWitness {
UnitWitness::nop(b"MEM")
}
fn setup_executor(mut rng: impl RngCore, ledger: LedgerState) -> ExecutorState {
let mut exec_state = ExecutorState::default();
exec_state.ledger = ledger;
let nmo_fund = InputWitness {
state: [0u8; 32],
value: 1348,
unit_witness: nmo(),
nonce: Nonce::random(&mut rng),
zone_id: ZONE_ID,
nf_sk: NullifierSecret::zero(),
};
let ((mmr, mmr_proof), _) = exec_state.observe_cm(&nmo_fund.note_commitment());
exec_state.set_fund_note(nmo_fund, mmr, mmr_proof);
let mem_fund = InputWitness {
state: [0u8; 32],
value: 14102,
unit_witness: mem(),
nonce: Nonce::random(&mut rng),
zone_id: ZONE_ID,
nf_sk: NullifierSecret::zero(),
};
let ((mmr, mmr_proof), _) = exec_state.observe_cm(&mem_fund.note_commitment());
exec_state.set_fund_note(mem_fund, mmr, mmr_proof);
// HACK: we don't currently support liquidity notes, we directly hard code the corresponding liquidity
// in the swapvm instead of minting pool LP tokens
exec_state
.swapvm
.add_liquidity(nmo().unit(), mem().unit(), nmo_fund.value, mem_fund.value);
exec_state
}
#[test]
fn simple_swap() {
let mut rng = rand::thread_rng();
let ledger = LedgerState::default();
// ---- setup scenario ----
let alice_sk = NullifierSecret::random(&mut rng);
let alice_in = InputWitness {
state: [0u8; 32],
value: 10,
unit_witness: nmo(),
nonce: Nonce::random(&mut rng),
zone_id: ZONE_ID,
nf_sk: alice_sk,
};
let mut exec_state = setup_executor(&mut rng, ledger);
let (alice_in_proof, _) = exec_state.observe_cm(&alice_in.note_commitment());
// ----- end setup ----
// Alice now has a valid 10 NMO note, she wants to swap it for 90 MEM
// ---- begin swap ----
let old_zone_state = exec_state.zone_state();
let old_zone_data = exec_state.swapvm.clone();
let mut temp_ledger_state = exec_state.ledger.clone();
let swap_goal_nonce = Nonce::random(&mut rng);
let swap_tx = TxWitness::default()
.add_input(alice_in, alice_in_proof)
.add_output(
app::swap_goal_note(swap_goal_nonce).to_output(),
app::SwapArgs {
output: app::SwapOutput::basic(mem().unit(), ZONE_ID, alice_sk.commit(), &mut rng),
limit: 90,
nonce: swap_goal_nonce,
},
);
let swap_tx_proof = ProvedTx::prove(swap_tx, vec![], vec![]).unwrap();
//
// alice ---- (swap_tx, swap_tx_proof) ---> executor
//
// alice sends the tx to an executor
exec_state.process_tx(&swap_tx_proof.public());
// the executor builds the solving tx
let (exec_tx, fund_notes) = exec_state.update_and_get_executor_tx();
let proved_exec_tx = ProvedTx::prove(exec_tx, vec![], vec![]).unwrap();
let swap_bundle = BundleWitness {
txs: vec![swap_tx_proof.public(), proved_exec_tx.public()],
};
let swap_bundle_proof = ProvedBundle::prove(vec![swap_tx_proof, proved_exec_tx]);
exec_state.ledger.add_bundle(swap_bundle.root());
exec_state.observe_nfs(
swap_bundle
.clone()
.commit()
.updates
.get(&exec_state.swapvm.zone_id)
.unwrap()
.into_iter()
.flat_map(|u| u.inputs.iter().copied())
.collect::<Vec<_>>(),
);
// prove stf
let stf_proof = StfPrivate {
zone_data: old_zone_data,
old_ledger: temp_ledger_state.to_witness(),
new_ledger: exec_state.ledger.clone().to_witness().commit(),
fund_notes,
bundle: swap_bundle,
}
.prove(risc0_zkvm::default_prover().as_ref())
.unwrap();
let ledger_proof =
ProvedLedgerTransition::prove(&mut temp_ledger_state, ZONE_ID, vec![swap_bundle_proof]);
let new_zone_state = exec_state.zone_state();
assert_eq!(ledger_proof.public().old_ledger, old_zone_state.ledger);
assert_eq!(ledger_proof.public().ledger, new_zone_state.ledger);
let zone_update = ProvedBatchUpdate {
batch: BatchUpdate {
updates: vec![Update {
old: old_zone_state,
new: new_zone_state,
}],
},
ledger_proofs: vec![ledger_proof],
stf_proofs: vec![stf_proof],
};
assert!(zone_update.verify())
}

View File

@ -10,4 +10,4 @@ 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" }
ledger_risc0_proof = { path = "../../../../../cl/ledger_risc0_proof" }

View File

@ -1,79 +1,81 @@
use app::{StateUpdate, ZoneData, ZoneOp};
use app::{SwapArgs, ZoneData};
use cl::{
crust::Tx,
mantle::{ledger::Ledger, zone::ZoneState},
};
use ledger_proof_statements::{
ledger::{LedgerProofPublic, SyncLog},
stf::StfPublic,
crust::{BundleWitness, InputWitness},
mantle::{
ledger::{Ledger, LedgerWitness},
zone::ZoneState,
},
};
use ledger_proof_statements::stf::StfPublic;
use risc0_zkvm::guest::env;
fn main() {
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 mut ledger_witness: LedgerWitness = env::read();
let new_ledger: Ledger = env::read();
let stf: [u8; 32] = env::read();
let ops: Vec<ZoneOp> = env::read();
let update_tx: StateUpdate = env::read();
let mut bundle: BundleWitness = env::read();
let pools_notes: Vec<InputWitness> = env::read();
let zone_id = zone_data.zone_id;
let old_zone_data = zone_data.commit();
for op in &ops {
zone_data.process_op(op);
}
let txs: Vec<&Tx> = ops
.iter()
.filter_map(|op| match op {
ZoneOp::Swap(_) => None,
ZoneOp::AddLiquidity { tx, .. } => Some(tx),
ZoneOp::RemoveLiquidity { tx, .. } => Some(tx),
ZoneOp::Ledger(tx) => Some(tx),
})
.chain(std::iter::once(&update_tx.tx))
.collect();
let outputs = txs
.iter()
.flat_map(|tx| tx.updates.iter().filter(|u| u.zone_id == zone_id))
.flat_map(|u| u.outputs.iter())
.copied()
.collect();
// TODO: inputs missings from ledger proof public
let _inputs: Vec<_> = txs
.iter()
.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,
ledger,
id: zone_id,
sync_logs,
outputs,
let old_state = ZoneState {
ledger: ledger_witness.commit(),
zone_data: zone_data.commit(),
stf,
};
env::verify(
ledger_validity_proof::LEDGER_ID,
&risc0_zkvm::serde::to_vec(&ledger_public).unwrap(),
)
.unwrap();
ledger_witness.add_bundle(bundle.root());
// ensure that we've seen every bundle in this ledger update
assert_eq!(ledger_witness.bundles.commit(), new_ledger.bundles_root);
// The last bundle should be a single executor tx that updates the zone data
let executor_tx = bundle.txs.pop().unwrap();
for tx in bundle.txs {
let Some(zone_update) = tx.updates.get(&zone_id) else {
// this tx does not concern this zone, ignore it.
continue;
};
assert!(zone_data.validate_no_pools(zone_update));
// is it a SWAP?
if tx
.balance
.unit_balance(app::swap_goal_unit().unit())
.is_neg()
{
// This TX encodes a SWAP request.
// as a simplifying assumption, we will assume that the SWAP goal note is the only output
// and a single input represents the funds provided by the user for the swap.
assert_eq!(zone_update.outputs.len(), 1);
assert_eq!(zone_update.inputs.len(), 1);
let (swap_goal_cm, swap_args_bytes) = &zone_update.outputs[0];
let swap_args: SwapArgs = cl::deserialize(&swap_args_bytes);
// ensure the witness corresponds to the swap goal cm
assert_eq!(
swap_goal_cm,
&app::swap_goal_note(swap_args.nonce).note_commitment()
);
let funds = tx
.balance
.balances
.iter()
.find(|bal| bal.unit != app::swap_goal_unit().unit())
.unwrap();
let t_in = funds.unit;
let amount_in = funds.pos - funds.neg;
zone_data.swap(t_in, amount_in, swap_args);
}
}
let public = StfPublic {
old: ZoneState {
ledger: old_ledger,
zone_data: old_zone_data,
stf,
},
old: old_state,
new: ZoneState {
ledger,
zone_data: zone_data.update_and_commit(&update_tx),
ledger: new_ledger,
zone_data: zone_data.update_and_commit(&executor_tx, &pools_notes),
stf,
},
};

View File

@ -4,12 +4,10 @@ members = [
"cl",
"ledger",
"ledger_proof_statements",
"risc0_proofs",
"ledger_risc0_proof",
"bundle_risc0_proof",
"tx_risc0_proof",
"ledger_validity_proof",
"risc0_proofs",
"risc0_images",
"risc0_images_police",
]
# Always optimize; building and running the risc0_proofs takes much longer without optimization.

View File

@ -1,11 +1,17 @@
[package]
name = "nomos_mantle_bundle_risc0_proof"
name = "bundle_risc0_proof"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "1.0" }
[dependencies]
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
cl = { path = "../cl" }
risc0_images = { path = "../risc0_images" }
hex = "0.4"
[package.metadata.risc0]
methods = ["bundle"]
[patch.crates-io]
# add RISC Zero accelerator support for all downstream usages of the following crates.
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }

View File

@ -1,3 +0,0 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -1,20 +0,0 @@
[package]
name = "bundle"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
serde = { version = "1.0", features = ["derive"] }
cl = { path = "../../cl" }
ledger_proof_statements = { path = "../../ledger_proof_statements" }
risc0_images = { path = "../../risc0_images" }
[patch.crates-io]
# add RISC Zero accelerator support for all downstream usages of the following crates.
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }

View File

@ -1,4 +1,5 @@
use cl::crust::BundleWitness;
use hex::FromHex;
use risc0_zkvm::{guest::env, serde};
fn main() {
@ -6,7 +7,7 @@ fn main() {
for tx in &bundle_private.txs {
env::verify(
risc0_images::nomos_mantle_tx_risc0_proof::TX_ID,
<[u8; 32]>::from_hex(risc0_images::TX_ID).unwrap(),
&serde::to_vec(&tx).unwrap(),
)
.unwrap();

View File

@ -1 +0,0 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));

View File

@ -3,22 +3,15 @@ name = "cl"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = {version="1.0", features = ["derive"]}
group = "0.13.0"
rand = "0.8.5"
rand_core = "0.6.0"
hex = "0.4.3"
curve25519-dalek = {version = "4.1", features = ["serde", "digest", "rand_core"]}
sha2 = "0.10"
lazy_static = "1.5.0"
risc0-zkvm = "1.2"
itertools = "0.14"
digest = "0.10"
bincode = "1"
[dev-dependencies]
rand = "0.8.5"
proptest = "1.2.0"
proptest-macro = "0.1"

View File

@ -14,6 +14,15 @@ pub struct UnitWitness {
}
impl UnitWitness {
pub fn nop(args: &[u8]) -> Self {
Self {
spending_covenant: NOP_COVENANT,
minting_covenant: NOP_COVENANT,
burning_covenant: NOP_COVENANT,
arg: crate::hash(args),
}
}
pub fn unit(&self) -> Unit {
let mut hasher = Hash::new();
hasher.update(b"NOMOS_CL_UNIT");
@ -33,24 +42,32 @@ pub struct UnitBalance {
}
impl UnitBalance {
pub fn is_zero(&self) -> bool {
self.pos == self.neg
}
pub fn pos(unit: Unit, value: u64) -> Self {
pub fn zero(unit: Unit) -> Self {
Self {
unit,
pos: value,
pos: 0,
neg: 0,
}
}
pub fn neg(unit: Unit, value: u64) -> Self {
Self {
unit,
pos: 0,
neg: value,
}
pub fn pos(unit: Unit, pos: u64) -> Self {
Self { unit, pos, neg: 0 }
}
pub fn neg(unit: Unit, neg: u64) -> Self {
Self { unit, pos: 0, neg }
}
pub fn is_zero(&self) -> bool {
self.pos == self.neg
}
pub fn is_neg(&self) -> bool {
self.neg > self.pos
}
pub fn is_pos(&self) -> bool {
self.pos > self.neg
}
}
@ -65,6 +82,14 @@ impl Balance {
}
}
pub fn unit_balance(&self, unit: Unit) -> UnitBalance {
self.balances
.iter()
.find(|b| b.unit == unit)
.cloned()
.unwrap_or_else(|| UnitBalance::zero(unit))
}
pub fn insert_positive(&mut self, unit: Unit, value: Value) {
for unit_bal in self.balances.iter_mut() {
if unit_bal.unit == unit {

View File

@ -6,7 +6,7 @@ use crate::{
mantle::ZoneId,
Digest, Hash,
};
use rand::RngCore;
use rand_core::RngCore;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,6 @@
use crate::crust::{balance::Unit, nullifier::NullifierCommitment};
use crate::mantle::ZoneId;
use crate::{Digest, Hash};
use rand::RngCore;
use serde::{Deserialize, Serialize};
#[cfg(test)]

View File

@ -15,7 +15,7 @@ use crate::{
};
/// An identifier of a transaction
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub struct TxRoot(pub [u8; 32]);
/// An identifier of a bundle
@ -50,7 +50,8 @@ impl TxRoot {
pub struct Tx {
pub root: TxRoot,
pub balance: Balance,
pub updates: Vec<LedgerUpdate>,
pub updates: BTreeMap<ZoneId, LedgerUpdate>,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
@ -63,23 +64,15 @@ pub struct TxWitness {
pub frontier_paths: Vec<(MMR, MMRProof)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct LedgerUpdate {
pub zone_id: ZoneId,
pub frontier_nodes: Vec<Root>,
pub inputs: Vec<Nullifier>,
pub outputs: Vec<NoteCommitment>,
}
pub struct LedgerUpdateWitness {
pub zone_id: ZoneId,
pub frontier_nodes: Vec<Root>,
pub inputs: Vec<Nullifier>,
pub outputs: Vec<(NoteCommitment, Vec<u8>)>,
}
impl LedgerUpdateWitness {
pub fn commit(self) -> (LedgerUpdate, [u8; 32]) {
impl LedgerUpdate {
pub fn root(&self, zone_id: ZoneId) -> [u8; 32] {
let input_root = merkle::root(&merkle::padded_leaves(&self.inputs));
let output_root = merkle::root(&merkle::padded_leaves(self.outputs.iter().map(
|(cm, data)| {
@ -88,67 +81,83 @@ impl LedgerUpdateWitness {
.collect::<Vec<_>>()
},
)));
let root = merkle::root(&merkle::padded_leaves([
input_root,
output_root,
self.zone_id,
]));
merkle::root(&merkle::padded_leaves([zone_id, input_root, output_root]))
}
(
LedgerUpdate {
zone_id: self.zone_id,
inputs: self.inputs,
outputs: self.outputs.into_iter().map(|(cm, _)| cm).collect(),
frontier_nodes: self.frontier_nodes,
},
root,
)
pub fn add_input(&mut self, nf: Nullifier, mmr: MMR) -> &mut Self {
self.inputs.push(nf);
self.frontier_nodes.extend(mmr.roots);
self.frontier_nodes.sort();
self.frontier_nodes.dedup();
self
}
pub fn add_output(&mut self, cm: NoteCommitment, data: Vec<u8>) -> &mut Self {
self.outputs.push((cm, data));
self
}
pub fn has_input(&self, nf: &Nullifier) -> bool {
self.inputs.contains(nf)
}
pub fn has_output(&self, cm: &NoteCommitment) -> bool {
self.outputs.iter().any(|(out_cm, _data)| out_cm == cm)
}
}
impl TxWitness {
pub fn add_input(mut self, input: InputWitness, input_cm_proof: (MMR, MMRProof)) -> Self {
assert!(input_cm_proof
.0
.verify_proof(&input.note_commitment().0, &input_cm_proof.1));
for (i, other_input) in self.inputs.iter().enumerate() {
if other_input.zone_id == input.zone_id {
// ensure a single MMR per zone per tx
assert_eq!(self.frontier_paths[i].0, input_cm_proof.0);
}
}
self.inputs.push(input);
self.frontier_paths.push(input_cm_proof);
self
}
pub fn add_output(mut self, output: OutputWitness, data: Vec<u8>) -> Self {
self.outputs.push((output, data));
pub fn add_output(mut self, output: OutputWitness, data: impl Serialize) -> Self {
self.outputs.push((output, crate::serialize(data)));
self
}
pub fn compute_updates(&self, inputs: &[InputDerivedFields]) -> Vec<LedgerUpdateWitness> {
let mut updates = BTreeMap::new();
assert_eq!(self.inputs.len(), self.frontier_paths.len());
pub fn compute_updates(&self, inputs: &[InputDerivedFields]) -> BTreeMap<ZoneId, LedgerUpdate> {
let mut updates: BTreeMap<ZoneId, LedgerUpdate> = Default::default();
assert_eq!(
self.inputs.len(),
self.frontier_paths.len(),
"out: {} {}",
self.inputs.len(),
self.frontier_paths.len()
);
for (input, (mmr, path)) in inputs.iter().zip(&self.frontier_paths) {
let entry = updates.entry(input.zone_id).or_insert(LedgerUpdateWitness {
zone_id: input.zone_id,
inputs: vec![],
outputs: vec![],
frontier_nodes: mmr.roots.clone(),
});
entry.inputs.push(input.nf);
assert!(mmr.verify_proof(&input.cm.0, path));
// ensure a single MMR per zone per tx
assert_eq!(&mmr.roots, &entry.frontier_nodes);
updates
.entry(input.zone_id)
.or_default()
.add_input(input.nf, mmr.clone());
}
for (output, data) in &self.outputs {
assert!(output.value > 0);
updates
.entry(output.zone_id)
.or_insert(LedgerUpdateWitness {
zone_id: output.zone_id,
inputs: vec![],
outputs: vec![],
frontier_nodes: vec![],
})
.outputs
.push((output.note_commitment(), data.clone())); // TODO: avoid clone
.or_default()
.add_output(output.note_commitment(), data.clone()); // TODO: avoid clone
}
updates.into_values().collect()
updates
}
pub fn mint_amounts(&self) -> Vec<MintAmount> {
@ -232,12 +241,12 @@ impl TxWitness {
) -> Tx {
let mint_burn_root = Self::mint_burn_root(mints, burns);
let (updates, updates_roots): (Vec<_>, Vec<_>) = self
.compute_updates(inputs)
.into_iter()
.map(LedgerUpdateWitness::commit)
.unzip();
let update_root = merkle::root(&merkle::padded_leaves(updates_roots));
let updates = self.compute_updates(inputs);
let update_root = merkle::root(&merkle::padded_leaves(
updates
.iter()
.map(|(zone_id, update)| update.root(*zone_id)),
));
let root = self.root(update_root, mint_burn_root);
let balance = self.balance(mints, burns);
@ -245,13 +254,14 @@ impl TxWitness {
root,
balance,
updates,
data: self.data.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Bundle {
pub updates: Vec<LedgerUpdate>,
pub updates: BTreeMap<ZoneId, Vec<LedgerUpdate>>,
pub root: BundleRoot,
}
@ -261,44 +271,26 @@ pub struct BundleWitness {
}
impl BundleWitness {
pub fn root(&self) -> BundleRoot {
BundleRoot(merkle::root(&merkle::padded_leaves(
self.txs.iter().map(|tx| tx.root.0),
)))
}
pub fn commit(self) -> Bundle {
assert!(Balance::combine(self.txs.iter().map(|tx| &tx.balance)).is_zero());
let root = BundleRoot(merkle::root(&merkle::padded_leaves(
self.txs.iter().map(|tx| tx.root.0),
)));
let root = self.root();
let updates = self
.txs
.into_iter()
.fold(BTreeMap::new(), |mut updates, tx| {
for update in tx.updates {
let entry = updates.entry(update.zone_id).or_insert(LedgerUpdate {
zone_id: update.zone_id,
inputs: vec![],
outputs: vec![],
frontier_nodes: vec![],
});
entry.inputs.extend(update.inputs);
entry.outputs.extend(update.outputs);
entry.frontier_nodes.extend(update.frontier_nodes); // TODO: maybe merge?
.fold(<BTreeMap<_, Vec<_>>>::new(), |mut updates, tx| {
for (zone_id, update) in tx.updates {
updates.entry(zone_id).or_default().push(update);
}
updates
})
.into_values()
.collect::<Vec<_>>();
// de-dup frontier nodes
let updates = updates
.into_iter()
.map(|mut update| {
update.frontier_nodes.sort();
update.frontier_nodes.dedup();
update
})
.collect();
});
Bundle { updates, root }
}

View File

@ -152,6 +152,22 @@ impl MMRProof {
let leaf = merkle::leaf(elem);
merkle::path_root(leaf, &self.path)
}
pub fn update(&mut self, elem: &[u8], folds: &MMRFolds) {
for (l, r) in &folds.folds {
let root = self.root(elem);
if &root == l {
self.path.push(merkle::PathNode::Right(*r))
} else if &root == r {
self.path.push(merkle::PathNode::Left(*l))
}
}
}
}
#[derive(Clone, Default)]
pub struct MMRFolds {
folds: Vec<([u8; 32], [u8; 32])>,
}
impl MMR {
@ -159,6 +175,25 @@ impl MMR {
Self::default()
}
pub fn folds(&self, elem: &[u8]) -> MMRFolds {
let mut folds = MMRFolds::default();
let mut height = 1;
let mut right = merkle::leaf(elem);
for i in (0..self.roots.len()).rev() {
if self.roots[i].height == height {
folds.folds.push((self.roots[i].root, right));
right = merkle::node(self.roots[i].root, right);
height += 1;
} else {
break;
}
}
folds
}
pub fn push(&mut self, elem: &[u8]) -> MMRProof {
let new_root = Root {
root: merkle::leaf(elem),
@ -304,4 +339,29 @@ mod test {
);
assert!(mmr.verify_proof(b"!", &proof));
}
#[test]
fn test_mmr_proof_update() {
let mut mmr = MMR::new();
let mut proofs = vec![];
for x in 'a'..='z' {
let b = [x as u8];
proofs.push((b, mmr.push(&b), mmr.clone()));
}
while !proofs.is_empty() {
let (x, mut x_pf, mut x_mmr) = proofs.remove(0);
assert!(x_mmr.verify_proof(&x, &x_pf));
for (y, _, y_mmr) in proofs.iter() {
x_pf.update(&x, x_mmr.folds(y));
assert!(y_mmr.verify_proof(&x, &x_pf));
x_mmr.push(y);
assert_eq!(&x_mmr, y_mmr);
}
}
}
}

View File

@ -2,11 +2,22 @@ pub mod crust;
pub mod ds;
pub mod mantle;
pub type Hash = risc0_zkvm::sha::rust_crypto::Sha256;
pub use digest::Digest;
pub use risc0_zkvm::sha::rust_crypto::{Digest, Sha256};
use serde::{de::DeserializeOwned, Serialize};
pub type Hash = Sha256;
pub fn hash(data: &[u8]) -> [u8; 32] {
let mut hasher = Hash::new();
hasher.update(data);
hasher.finalize().into()
}
// TODO: spec serializiation
pub fn serialize(data: impl Serialize) -> Vec<u8> {
bincode::serialize(&data).unwrap()
}
pub fn deserialize<T: DeserializeOwned>(bytes: &[u8]) -> T {
bincode::deserialize(bytes).unwrap()
}

View File

@ -1,20 +1,24 @@
use crate::{
crust::{NoteCommitment, Nullifier},
ds::indexed::{BatchUpdateProof, NullifierTree},
ds::mmr::{MMRProof, MMR},
crust::{BundleRoot, NoteCommitment, Nullifier},
ds::{
indexed::{BatchUpdateProof, NullifierTree},
mmr::{MMRProof, MMR},
},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Ledger {
cm_root: [u8; 32],
nf_root: [u8; 32],
pub cm_root: [u8; 32],
pub nf_root: [u8; 32],
pub bundles_root: [u8; 32],
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LedgerWitness {
pub commitments: MMR,
pub nf_root: [u8; 32],
pub bundles: MMR,
}
impl LedgerWitness {
@ -22,6 +26,7 @@ impl LedgerWitness {
Ledger {
cm_root: self.commitments.commit(),
nf_root: self.nf_root,
bundles_root: self.bundles.commit(),
}
}
@ -33,6 +38,10 @@ impl LedgerWitness {
self.commitments.push(&cm.0);
}
pub fn add_bundle(&mut self, bundle_root: BundleRoot) {
self.bundles.push(&bundle_root.0);
}
pub fn assert_nfs_update(&mut self, nullifiers: &[Nullifier], proof: &BatchUpdateProof) {
// update the nullifer root with the nullifier inserted into the tree
self.nf_root = proof.verify(nullifiers, self.nf_root);
@ -43,6 +52,7 @@ impl LedgerWitness {
pub struct LedgerState {
pub commitments: MMR,
pub nullifiers: NullifierTree,
pub bundles: MMR,
}
impl LedgerState {
@ -50,6 +60,7 @@ impl LedgerState {
LedgerWitness {
commitments: self.commitments.clone(),
nf_root: self.nf_root(),
bundles: self.bundles.clone(),
}
}
@ -65,4 +76,9 @@ impl LedgerState {
pub fn add_nullifiers(&mut self, nfs: Vec<Nullifier>) -> BatchUpdateProof {
self.nullifiers.insert_batch(nfs)
}
pub fn add_bundle(&mut self, bundle_root: BundleRoot) -> (MMR, MMRProof) {
let proof = self.bundles.push(&bundle_root.0);
(self.bundles.clone(), proof)
}
}

View File

@ -1,19 +1,28 @@
#!/usr/bin/env bash
set -e
# We generate in a *loop* because some risc0 proofs are recursive, so if a child
# proof's id changes, then the parent proof will also change, but we don't see the
# parent's id change until the next run.
# Order of these proofs is important, we sequence them topologically by composition order
proofs=$(cat <<EOF
risc0_proofs
bundle_risc0_proof
ledger_risc0_proof
EOF
)
cargo run --bin gen_risc0_images > risc0_images/src/lib.rs.new
for proof in $proofs; do
echo "Building $proof"
# Run the cargo risczero build command and process output line by line
cargo risczero build --manifest-path "$proof/Cargo.toml" | while read -r line; do
# Parse out the
if [[ $line =~ ImageID:\ ([0-9a-f]+)\ -\ \"(.+)\" ]]; then
image_id="${BASH_REMATCH[1]}"
image_elf="${BASH_REMATCH[2]}"
image_name=$(basename $image_elf | tr '[:lower:]' '[:upper:]')
echo "${image_name}_ID: $image_id"
echo "${image_name}_ELF: $(cat $image_elf | xxd -p | head -c 32)..."
while ! cmp -s risc0_images/src/lib.rs.new risc0_images/src/lib.rs
do
mv risc0_images/src/lib.rs.new risc0_images/src/lib.rs
cargo run --bin gen_risc0_images > risc0_images/src/lib.rs.new
echo "-------- FINISHED UPDATE ITERATION --------"
echo $image_id | tr -d '\n' > "risc0_images/src/${image_name}_ID"
cp $image_elf "risc0_images/src/${image_name}_ELF"
fi
done
done
rm risc0_images/src/lib.rs.new
cargo test -p risc0_images_police

View File

@ -6,9 +6,10 @@ edition = "2021"
[dependencies]
cl = { path = "../cl" }
ledger_proof_statements = { path = "../ledger_proof_statements" }
risc0_images = { path = "../risc0_images" }
risc0_images = { path = "../risc0_images", features = ["elf"]}
risc0-zkvm = { version = "1.0", features = ["prove", "metal"] }
risc0-groth16 = { version = "1.0" }
rand = "0.8.5"
rand_core = "0.6.0"
thiserror = "1.0.62"
hex = "0.4"

View File

@ -1,16 +1,22 @@
use crate::tx::ProvedTx;
use cl::crust::{Bundle, BundleWitness};
use hex::FromHex;
#[derive(Debug, Clone)]
pub struct ProvedBundle {
pub risc0_receipt: risc0_zkvm::Receipt,
}
impl ProvedBundle {
pub fn prove(bundle: &BundleWitness, txs: Vec<ProvedTx>) -> Self {
pub fn prove(txs: Vec<ProvedTx>) -> Self {
//show that all ptx's are individually valid, and balance to 0
let mut env = risc0_zkvm::ExecutorEnv::builder();
let bundle = BundleWitness {
txs: txs.iter().map(|tx| tx.public()).collect(),
};
for proved_tx in txs {
env.add_assumption(proved_tx.risc0_receipt);
}
@ -23,11 +29,7 @@ impl ProvedBundle {
let opts = risc0_zkvm::ProverOpts::succinct();
let prove_info = prover
.prove_with_opts(
env,
risc0_images::nomos_mantle_bundle_risc0_proof::BUNDLE_ELF,
&opts,
)
.prove_with_opts(env, risc0_images::BUNDLE_ELF, &opts)
.unwrap();
println!(
@ -50,7 +52,7 @@ impl ProvedBundle {
pub fn verify(&self) -> bool {
self.risc0_receipt
.verify(risc0_images::nomos_mantle_bundle_risc0_proof::BUNDLE_ID)
.verify(<[u8; 32]>::from_hex(risc0_images::BUNDLE_ID).unwrap())
.is_ok()
}
}

View File

@ -5,6 +5,8 @@ use ledger_proof_statements::ledger::{LedgerBundleWitness, LedgerProofPrivate, L
use crate::bundle::ProvedBundle;
use cl::mantle::{ledger::LedgerState, zone::ZoneId};
use hex::FromHex;
#[derive(Debug, Clone)]
pub struct ProvedLedgerTransition {
pub risc0_receipt: risc0_zkvm::Receipt,
@ -21,29 +23,28 @@ impl ProvedLedgerTransition {
let bundle = proved_bundle.public();
let zone_ledger_update = bundle
let zone_ledger_updates = bundle
.updates
.iter()
.find(|update| update.zone_id == zone_id)
.get(&zone_id)
.expect("why are we proving this bundle for this zone if it's not involved?");
let cm_root_proofs =
BTreeMap::from_iter(zone_ledger_update.frontier_nodes.iter().map(|root| {
// We make the simplifying assumption that bundle proofs
// are done w.r.t. the latest MMR (hence, empty merkle proofs)
//
// We can remove this assumption by tracking old MMR roots in the LedgerState
(root.root, vec![])
}));
nullifiers.extend(zone_ledger_update.inputs.clone());
let mut cm_root_proofs = BTreeMap::new();
for zone_ledger_update in zone_ledger_updates {
cm_root_proofs.extend(
zone_ledger_update
.frontier_nodes
.iter()
.map(|root| (root.root, vec![])),
);
nullifiers.extend(zone_ledger_update.inputs.clone());
}
let ledger_bundle = LedgerBundleWitness {
bundle,
cm_root_proofs,
};
w_bundles.push(ledger_bundle)
w_bundles.push(ledger_bundle);
}
let witness = LedgerProofPrivate {
@ -54,13 +55,19 @@ impl ProvedLedgerTransition {
};
for bundle in &witness.bundles {
for update in &bundle.bundle.updates {
if update.zone_id == zone_id {
for cm in &update.outputs {
ledger.add_commitment(cm);
}
let updates = bundle
.bundle
.updates
.get(&zone_id)
.expect("should have a bundle from the zone we are proofing for");
for update in updates {
for (cm, _data) in &update.outputs {
ledger.add_commitment(cm);
}
}
ledger.add_bundle(bundle.bundle.root);
}
witness.write(&mut env);
@ -75,7 +82,7 @@ impl ProvedLedgerTransition {
// This struct contains the receipt along with statistics about execution of the guest
let opts = risc0_zkvm::ProverOpts::succinct();
let prove_info = prover
.prove_with_opts(env, risc0_images::ledger_validity_proof::LEDGER_ELF, &opts)
.prove_with_opts(env, risc0_images::LEDGER_ELF, &opts)
.unwrap();
println!(
@ -99,7 +106,7 @@ impl ProvedLedgerTransition {
pub fn verify(&self) -> bool {
self.risc0_receipt
.verify(risc0_images::ledger_validity_proof::LEDGER_ID)
.verify(<[u8; 32]>::from_hex(risc0_images::LEDGER_ID).unwrap())
.is_ok()
}
}

View File

@ -1,9 +1,11 @@
use cl::mantle::zone::Stf;
use ledger_proof_statements::stf::StfPublic;
use hex::FromHex;
#[derive(Debug, Clone)]
pub struct StfProof {
pub risc0_id: [u32; 8],
pub risc0_id: [u8; 32],
pub public: StfPublic,
pub risc0_receipt: risc0_zkvm::Receipt,
}
@ -15,7 +17,7 @@ pub fn risc0_stf(risc0_id: [u32; 8]) -> Stf {
}
impl StfProof {
pub fn from_risc0(risc0_id: [u32; 8], risc0_receipt: risc0_zkvm::Receipt) -> Self {
pub fn from_risc0(risc0_id: [u8; 32], risc0_receipt: risc0_zkvm::Receipt) -> Self {
Self {
risc0_id,
public: risc0_receipt.journal.decode().unwrap(),
@ -24,14 +26,14 @@ impl StfProof {
}
pub fn stf(&self) -> Stf {
risc0_stf(self.risc0_id)
self.risc0_id
}
pub fn verify(&self) -> bool {
self.risc0_receipt.verify(self.risc0_id).is_ok()
}
pub fn nop_stf() -> [u8; 32] {
risc0_stf(risc0_images::nomos_mantle_risc0_proofs::STF_NOP_ID)
FromHex::from_hex(risc0_images::STF_NOP_ID).unwrap()
}
pub fn prove_nop(public: StfPublic) -> Self {
@ -47,11 +49,7 @@ impl StfProof {
let opts = risc0_zkvm::ProverOpts::succinct();
let prove_info = prover
.prove_with_opts(
env,
risc0_images::nomos_mantle_risc0_proofs::STF_NOP_ELF,
&opts,
)
.prove_with_opts(env, risc0_images::STF_NOP_ELF, &opts)
.unwrap();
println!(
@ -64,7 +62,7 @@ impl StfProof {
let receipt = prove_info.receipt;
Self {
risc0_id: risc0_images::nomos_mantle_risc0_proofs::STF_NOP_ID,
risc0_id: FromHex::from_hex(risc0_images::STF_NOP_ID).unwrap(),
public,
risc0_receipt: receipt,
}

View File

@ -3,6 +3,7 @@ use crate::{
error::{Error, Result},
};
use cl::crust::{Tx, TxWitness};
use hex::FromHex;
#[derive(Debug, Clone)]
pub struct ProvedTx {
@ -36,11 +37,7 @@ impl ProvedTx {
// This struct contains the receipt along with statistics about execution of the guest
let opts = risc0_zkvm::ProverOpts::succinct();
let prove_info = prover
.prove_with_opts(
env,
risc0_images::nomos_mantle_tx_risc0_proof::TX_ELF,
&opts,
)
.prove_with_opts(env, risc0_images::TX_ELF, &opts)
.map_err(|_| Error::Risc0ProofFailed)?;
println!(
@ -61,7 +58,7 @@ impl ProvedTx {
pub fn verify(&self) -> bool {
self.risc0_receipt
.verify(risc0_images::nomos_mantle_tx_risc0_proof::TX_ID)
.verify(<[u8; 32]>::from_hex(risc0_images::TX_ID).unwrap())
.is_ok()
}
}

View File

@ -45,15 +45,10 @@ impl ProvedBatchUpdate {
.zip(self.stf_proofs.iter())
.zip(self.ledger_proofs.iter())
{
if ledger_proof.public().old_ledger != update.old.ledger
|| ledger_proof.public().ledger != update.new.ledger
{
return false;
}
if stf_proof.public.old != update.old || stf_proof.public.new != update.new {
return false;
}
assert_eq!(ledger_proof.public().old_ledger, update.old.ledger);
assert_eq!(ledger_proof.public().ledger, update.new.ledger);
assert_eq!(stf_proof.public.old, update.old);
assert_eq!(stf_proof.public.new, update.new);
}
true

View File

@ -64,8 +64,8 @@ fn cross_transfer_transition(
let tx_witness = TxWitness::default()
.add_input(input, input_proof)
.add_output(transfer, vec![])
.add_output(change, vec![]);
.add_output(transfer, "")
.add_output(change, "");
let proved_tx = ProvedTx::prove(
tx_witness.clone(),
@ -74,12 +74,7 @@ fn cross_transfer_transition(
)
.unwrap();
let bundle = ProvedBundle::prove(
&BundleWitness {
txs: vec![proved_tx.public()],
},
vec![proved_tx],
);
let bundle = ProvedBundle::prove(vec![proved_tx]);
println!("proving ledger A transition");
let ledger_in_transition =

View File

@ -1,16 +1,15 @@
[package]
name = "stf_nop"
name = "ledger_risc0_proof"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
serde = { version = "1.0", features = ["derive"] }
cl = { path = "../../cl" }
ledger_proof_statements = { path = "../../ledger_proof_statements" }
cl = { path = "../cl" }
ledger_proof_statements = { path = "../ledger_proof_statements" }
risc0_images = { path = "../risc0_images" }
hex = "0.4"
[patch.crates-io]
# add RISC Zero accelerator support for all downstream usages of the following crates.

View File

@ -1,4 +1,5 @@
use cl::ds::merkle;
use hex::FromHex;
use ledger_proof_statements::ledger::{
LedgerBundleWitness, LedgerProofPrivate, LedgerProofPublic, SyncLog,
};
@ -23,42 +24,46 @@ fn main() {
} in bundles
{
env::verify(
risc0_images::nomos_mantle_bundle_risc0_proof::BUNDLE_ID,
<[u8; 32]>::from_hex(risc0_images::BUNDLE_ID).unwrap(),
&serde::to_vec(&bundle).unwrap(),
)
.unwrap();
let zones = Vec::from_iter(bundle.updates.iter().map(|update| update.zone_id));
if !(zones.len() == 1 && zones[0] == id) {
if bundle.updates.len() > 1 {
// This is a cross zone bundle, add a sync log for it to ensure all zones
// also approve it.
sync_logs.push(SyncLog {
bundle: bundle.root,
zones,
zones: bundle.updates.keys().copied().collect(),
});
}
if let Some(ledger_update) = bundle
let ledger_updates = bundle
.updates
.into_iter()
.filter(|update| update.zone_id == id)
.next()
{
.get(&id)
.expect("attempting to prove a bundle that is not for this zone");
for ledger_update in ledger_updates {
for node in &ledger_update.frontier_nodes {
let past_cm_root_proof = cm_root_proofs
.get(&node.root)
.expect("missing cm root proof");
let expected_current_cm_root = merkle::path_root(node.root, past_cm_root_proof);
assert!(old_ledger.valid_cm_root(expected_current_cm_root))
assert!(
old_ledger.valid_cm_root(expected_current_cm_root)
|| ledger.valid_cm_root(expected_current_cm_root)
);
}
for cm in &ledger_update.outputs {
for (cm, _data) in &ledger_update.outputs {
ledger.add_commitment(cm);
outputs.push(*cm)
outputs.push(*cm);
}
nullifiers.extend(ledger_update.inputs);
nullifiers.extend(ledger_update.inputs.clone());
}
ledger.add_bundle(bundle.root);
}
// TODO: sort outside and check

View File

@ -1,11 +0,0 @@
[package]
name = "ledger_validity_proof"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "1.0" }
[package.metadata.risc0]
methods = [ "ledger"]

View File

@ -1,3 +0,0 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -1,19 +0,0 @@
[package]
name = "ledger"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
serde = { version = "1.0", features = ["derive"] }
cl = { path = "../../cl" }
ledger_proof_statements = { path = "../../ledger_proof_statements" }
risc0_images = { path = "../../risc0_images" }
[patch.crates-io]
# add RISC Zero accelerator support for all downstream usages of the following crates.
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }

View File

@ -1 +0,0 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));

View File

@ -5,3 +5,7 @@ edition = "2021"
[dependencies]
binary_macros = "1.0.0"
[features]
default = []
elf = []

Binary file not shown.

View File

@ -0,0 +1 @@
2aceb7181ab1155497eaf932c71dfe8bd9fcab476c543d7e03163fe083b15e19

Binary file not shown.

View File

@ -0,0 +1 @@
4e5e4fdaa2448d4bb16aecf2b0c6279ef7a573df3ccb999201a1f1b7400f8732

Binary file not shown.

View File

@ -0,0 +1 @@
557a0804bbf1220e66f4a695c24d57de02cb9fd52d646cb99dd43572561a6807

Binary file not shown.

View File

@ -0,0 +1 @@
70c7de2aaee012e499f1a7c659d84ce35acadf59c8714931fd1e60cc7ef28067

File diff suppressed because one or more lines are too long

View File

@ -1,14 +0,0 @@
[package]
name = "risc0_images_police"
version = "0.1.0"
edition = "2021"
[dependencies]
sha2 = "0.10"
base64 = "0.22"
risc0_images = { path = "../risc0_images" }
nomos_mantle_risc0_proofs = { path = "../risc0_proofs" }
nomos_mantle_bundle_risc0_proof = { path = "../bundle_risc0_proof" }
nomos_mantle_tx_risc0_proof = { path = "../tx_risc0_proof" }
ledger_validity_proof = { path = "../ledger_validity_proof" }

View File

@ -1,25 +0,0 @@
use base64::prelude::*;
macro_rules! gen_risc0_image {
($module:ident, $id:ident, $elf:ident) => {
println!("pub mod {} {{", stringify!($module));
println!(
" pub const {}: [u32; 8] = {:?};",
stringify!($id),
$module::$id
);
println!(
" pub static {}: &[u8] = binary_macros::base64!({:?});",
stringify!($elf),
BASE64_STANDARD.encode(&$module::$elf)
);
println!("}}");
};
}
fn main() {
gen_risc0_image!(nomos_mantle_risc0_proofs, STF_NOP_ID, STF_NOP_ELF);
gen_risc0_image!(nomos_mantle_bundle_risc0_proof, BUNDLE_ID, BUNDLE_ELF);
gen_risc0_image!(nomos_mantle_tx_risc0_proof, TX_ID, TX_ELF);
gen_risc0_image!(ledger_validity_proof, LEDGER_ID, LEDGER_ELF);
}

View File

@ -1,51 +0,0 @@
#[cfg(test)]
mod tests {
fn hash(x: impl AsRef<[u8]>) -> [u8; 32] {
use sha2::{Digest, Sha256};
Sha256::digest(x).into()
}
#[test]
fn ensure_images_are_correct() {
assert_eq!(
risc0_images::nomos_mantle_risc0_proofs::STF_NOP_ID,
nomos_mantle_risc0_proofs::STF_NOP_ID,
"STF_NOP_ID"
);
assert_eq!(
hash(risc0_images::nomos_mantle_risc0_proofs::STF_NOP_ELF),
hash(nomos_mantle_risc0_proofs::STF_NOP_ELF),
"STF_NOP_ELF"
);
assert_eq!(
risc0_images::nomos_mantle_bundle_risc0_proof::BUNDLE_ID,
nomos_mantle_bundle_risc0_proof::BUNDLE_ID,
"BUNDLE_ID"
);
assert_eq!(
hash(risc0_images::nomos_mantle_bundle_risc0_proof::BUNDLE_ELF),
hash(nomos_mantle_bundle_risc0_proof::BUNDLE_ELF),
"BUNDLE_ELF"
);
assert_eq!(
risc0_images::nomos_mantle_tx_risc0_proof::TX_ID,
nomos_mantle_tx_risc0_proof::TX_ID,
"TX_ID"
);
assert_eq!(
hash(risc0_images::nomos_mantle_tx_risc0_proof::TX_ELF),
hash(nomos_mantle_tx_risc0_proof::TX_ELF),
"TX_ELF"
);
assert_eq!(
risc0_images::ledger_validity_proof::LEDGER_ID,
ledger_validity_proof::LEDGER_ID,
"LEDGER_ID"
);
assert_eq!(
hash(risc0_images::ledger_validity_proof::LEDGER_ELF),
hash(ledger_validity_proof::LEDGER_ELF),
"LEDGER_ELF"
);
}
}

View File

@ -1,11 +1,17 @@
[package]
name = "nomos_mantle_risc0_proofs"
name = "risc0_proofs"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "1.0" }
[dependencies]
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
serde = { version = "1.0", features = ["derive"] }
cl = { path = "../cl" }
ledger_proof_statements = { path = "../ledger_proof_statements" }
[package.metadata.risc0]
methods = ["stf_nop"]
[patch.crates-io]
# add RISC Zero accelerator support for all downstream usages of the following crates.
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }

View File

@ -1,3 +0,0 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -1 +0,0 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));

View File

@ -1,11 +0,0 @@
[package]
name = "nomos_mantle_tx_risc0_proof"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "1.0" }
[package.metadata.risc0]
methods = ["tx"]

View File

@ -1,3 +0,0 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -1 +0,0 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));

View File

@ -1,19 +0,0 @@
[package]
name = "tx"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] }
serde = { version = "1.0", features = ["derive"] }
cl = { path = "../../cl" }
ledger_proof_statements = { path = "../../ledger_proof_statements" }
[patch.crates-io]
# add RISC Zero accelerator support for all downstream usages of the following crates.
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" }
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" }