From 9ca5e8af57ff4bbcd98bf07229ced9ff40da52a7 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sun, 21 Jul 2024 19:05:35 +0400 Subject: [PATCH] goas: output + partial_tx + bundle proofs all working together! --- goas/cl/cl/src/note.rs | 20 ++-- goas/cl/ledger/Cargo.toml | 1 + goas/cl/ledger/src/bundle.rs | 57 ++++++++++ goas/cl/ledger/src/input.rs | 98 +++++------------ goas/cl/ledger/src/lib.rs | 4 + goas/cl/ledger/src/output.rs | 103 ++++++++++++++++++ goas/cl/ledger/src/partial_tx.rs | 26 +++++ goas/cl/ledger/tests/simple_transfer.rs | 49 +++++++++ .../proof_statements/src/death_constraint.rs | 1 - goas/cl/proof_statements/src/lib.rs | 1 + goas/cl/proof_statements/src/ptx.rs | 1 + goas/cl/risc0_proofs/Cargo.toml | 2 +- goas/cl/risc0_proofs/bundle/Cargo.toml | 19 ++++ goas/cl/risc0_proofs/bundle/src/main.rs | 17 +++ .../death_constraint_nop/Cargo.toml | 19 ++++ .../death_constraint_nop/src/main.rs | 8 ++ goas/cl/risc0_proofs/output/Cargo.toml | 19 ++++ goas/cl/risc0_proofs/output/src/main.rs | 12 ++ 18 files changed, 375 insertions(+), 82 deletions(-) create mode 100644 goas/cl/ledger/src/bundle.rs create mode 100644 goas/cl/ledger/src/output.rs create mode 100644 goas/cl/ledger/src/partial_tx.rs create mode 100644 goas/cl/ledger/tests/simple_transfer.rs create mode 100644 goas/cl/risc0_proofs/bundle/Cargo.toml create mode 100644 goas/cl/risc0_proofs/bundle/src/main.rs create mode 100644 goas/cl/risc0_proofs/death_constraint_nop/Cargo.toml create mode 100644 goas/cl/risc0_proofs/death_constraint_nop/src/main.rs create mode 100644 goas/cl/risc0_proofs/output/Cargo.toml create mode 100644 goas/cl/risc0_proofs/output/src/main.rs diff --git a/goas/cl/cl/src/note.rs b/goas/cl/cl/src/note.rs index 44ef951..f853476 100644 --- a/goas/cl/cl/src/note.rs +++ b/goas/cl/cl/src/note.rs @@ -40,22 +40,26 @@ pub struct NoteWitness { } impl NoteWitness { - pub fn new(value: u64, unit: impl Into, state: [u8; 32]) -> Self { + pub fn new( + value: u64, + unit: impl Into, + death_constraint: [u8; 32], + state: [u8; 32], + ) -> Self { Self { value, unit: unit_point(&unit.into()), - death_constraint: [0u8; 32], + death_constraint, state, } } pub fn basic(value: u64, unit: impl Into) -> Self { - Self { - value, - unit: unit_point(&unit.into()), - death_constraint: [0u8; 32], - state: [0u8; 32], - } + Self::new(value, unit, [0u8; 32], [0u8; 32]) + } + + pub fn stateless(value: u64, unit: impl Into, death_constraint: [u8; 32]) -> Self { + Self::new(value, unit, death_constraint, [0u8; 32]) } pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment { diff --git a/goas/cl/ledger/Cargo.toml b/goas/cl/ledger/Cargo.toml index a0767f2..aea2bf7 100644 --- a/goas/cl/ledger/Cargo.toml +++ b/goas/cl/ledger/Cargo.toml @@ -10,4 +10,5 @@ nomos_cl_risc0_proofs = { path = "../risc0_proofs" } 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" diff --git a/goas/cl/ledger/src/bundle.rs b/goas/cl/ledger/src/bundle.rs new file mode 100644 index 0000000..096a8aa --- /dev/null +++ b/goas/cl/ledger/src/bundle.rs @@ -0,0 +1,57 @@ +use crate::error::Result; + +pub struct ProvedBundle { + pub bundle: cl::Bundle, + pub risc0_receipt: risc0_zkvm::Receipt, +} + +impl ProvedBundle { + pub fn prove(bundle: &cl::Bundle, balance_witness: &cl::BalanceWitness) -> Self { + // need to show that bundle is balanced. + // i.e. the sum of ptx balances is 0 + + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&balance_witness) + .unwrap() + .build() + .unwrap(); + + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::Instant::now(); + + let opts = risc0_zkvm::ProverOpts::succinct(); + let prove_info = prover + .prove_with_opts(env, nomos_cl_risc0_proofs::BUNDLE_ELF, &opts) + .unwrap(); + + println!( + "STARK 'bundle' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + let receipt = prove_info.receipt; + + Self { + bundle: bundle.clone(), + risc0_receipt: receipt, + } + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self) -> bool { + let Ok(zero_commitment) = self.public() else { + return false; + }; + + self.bundle.balance() == zero_commitment + && self + .risc0_receipt + .verify(nomos_cl_risc0_proofs::BUNDLE_ID) + .is_ok() + } +} diff --git a/goas/cl/ledger/src/input.rs b/goas/cl/ledger/src/input.rs index 7e3d969..3d5e60d 100644 --- a/goas/cl/ledger/src/input.rs +++ b/goas/cl/ledger/src/input.rs @@ -6,11 +6,11 @@ const MAX_NOTE_COMMS: usize = 2usize.pow(8); pub struct ProvedInput { pub input: InputPublic, - pub proof: InputProof, + pub risc0_receipt: risc0_zkvm::Receipt, } impl ProvedInput { - pub fn prove(input: cl::InputWitness, note_commitments: &[cl::NoteCommitment]) -> Self { + pub fn prove(input: &cl::InputWitness, note_commitments: &[cl::NoteCommitment]) -> Self { let output_cm = input.to_output().commit_note(); let cm_leaves = note_commitment_leaves(note_commitments); @@ -20,7 +20,10 @@ impl ProvedInput { .unwrap(); let cm_path = cl::merkle::path(cm_leaves, cm_idx); - let secrets = InputPrivate { input, cm_path }; + let secrets = InputPrivate { + input: *input, + cm_path, + }; let env = risc0_zkvm::ExecutorEnv::builder() .write(&secrets) @@ -31,8 +34,7 @@ impl ProvedInput { // Obtain the default prover. let prover = risc0_zkvm::default_prover(); - use std::time::Instant; - let start_t = Instant::now(); + let start_t = std::time::Instant::now(); // Proof information by proving the specified ELF binary. // This struct contains the receipt along with statistics about execution of the guest @@ -54,34 +56,31 @@ impl ProvedInput { cm_root: cl::merkle::root(cm_leaves), input: input.commit(), }, - proof: InputProof { receipt }, + risc0_receipt: receipt, } } + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + pub fn verify(&self) -> bool { - self.proof.verify(&self.input) + let Ok(proved_public_inputs) = self.public() else { + return false; + }; + + self.input == proved_public_inputs + && self + .risc0_receipt + .verify(nomos_cl_risc0_proofs::INPUT_ID) + .is_ok() } } #[derive(Debug, Clone)] -pub struct InputProof { - receipt: risc0_zkvm::Receipt, -} +pub struct InputProof {} -impl InputProof { - pub fn public(&self) -> Result { - Ok(self.receipt.journal.decode()?) - } - - pub fn verify(&self, expected_public_inputs: &InputPublic) -> bool { - let Ok(public_inputs) = self.public() else { - return false; - }; - - &public_inputs == expected_public_inputs - && self.receipt.verify(nomos_cl_risc0_proofs::INPUT_ID).is_ok() - } -} +impl InputProof {} fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; MAX_NOTE_COMMS] { let note_comm_bytes = Vec::from_iter(note_commitments.iter().map(|c| c.as_bytes().to_vec())); @@ -109,7 +108,7 @@ mod test { let notes = vec![input.to_output().commit_note()]; - let proved_input = ProvedInput::prove(input, ¬es); + let mut proved_input = ProvedInput::prove(&input, ¬es); let expected_public_inputs = InputPublic { cm_root: cl::merkle::root(note_commitment_leaves(¬es)), @@ -152,57 +151,12 @@ mod test { ]; for wrong_input in wrong_public_inputs { - assert!(!proved_input.proof.verify(&wrong_input)); + proved_input.input = wrong_input; + assert!(!proved_input.verify()); } } // ----- The following tests still need to be built. ----- - // - // #[test] - // fn test_input_proof() { - // let mut rng = rand::thread_rng(); - - // let ptx_root = cl::PtxRoot::default(); - - // let note = cl::NoteWitness::new(10, "NMO", [0u8; 32], &mut rng); - // let nf_sk = cl::NullifierSecret::random(&mut rng); - // let nonce = cl::NullifierNonce::random(&mut rng); - - // let input_witness = cl::InputWitness { note, nf_sk, nonce }; - - // let input = input_witness.commit(); - // let proof = input.prove(&input_witness, ptx_root, vec![]).unwrap(); - - // assert!(input.verify(ptx_root, &proof)); - - // let wrong_witnesses = [ - // cl::InputWitness { - // note: cl::NoteWitness::new(11, "NMO", [0u8; 32], &mut rng), - // ..input_witness.clone() - // }, - // cl::InputWitness { - // note: cl::NoteWitness::new(10, "ETH", [0u8; 32], &mut rng), - // ..input_witness.clone() - // }, - // cl::InputWitness { - // nf_sk: cl::NullifierSecret::random(&mut rng), - // ..input_witness.clone() - // }, - // cl::InputWitness { - // nonce: cl::NullifierNonce::random(&mut rng), - // ..input_witness.clone() - // }, - // ]; - - // for wrong_witness in wrong_witnesses { - // assert!(input.prove(&wrong_witness, ptx_root, vec![]).is_err()); - - // let wrong_input = wrong_witness.commit(); - // let wrong_proof = wrong_input.prove(&wrong_witness, ptx_root, vec![]).unwrap(); - // assert!(!input.verify(ptx_root, &wrong_proof)); - // } - // } - // #[test] // fn test_input_ptx_coupling() { // let mut rng = rand::thread_rng(); diff --git a/goas/cl/ledger/src/lib.rs b/goas/cl/ledger/src/lib.rs index 2150f1b..bac9e97 100644 --- a/goas/cl/ledger/src/lib.rs +++ b/goas/cl/ledger/src/lib.rs @@ -1,2 +1,6 @@ +// pub mod death_constraint; +pub mod bundle; pub mod error; pub mod input; +pub mod output; +pub mod partial_tx; diff --git a/goas/cl/ledger/src/output.rs b/goas/cl/ledger/src/output.rs new file mode 100644 index 0000000..0ad6ee3 --- /dev/null +++ b/goas/cl/ledger/src/output.rs @@ -0,0 +1,103 @@ +use crate::error::Result; + +pub struct ProvedOutput { + pub output: cl::Output, + pub risc0_receipt: risc0_zkvm::Receipt, +} + +impl ProvedOutput { + pub fn prove(witness: &cl::OutputWitness) -> Self { + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&witness) + .unwrap() + .build() + .unwrap(); + + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::Instant::now(); + + let opts = risc0_zkvm::ProverOpts::succinct(); + let prove_info = prover + .prove_with_opts(env, nomos_cl_risc0_proofs::OUTPUT_ELF, &opts) + .unwrap(); + + println!( + "STARK 'output' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + let receipt = prove_info.receipt; + + Self { + output: witness.commit(), + risc0_receipt: receipt, + } + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self) -> bool { + let Ok(output_commitments) = self.public() else { + return false; + }; + + self.output == output_commitments + && self + .risc0_receipt + .verify(nomos_cl_risc0_proofs::OUTPUT_ID) + .is_ok() + } +} + +#[cfg(test)] +mod test { + use rand::thread_rng; + + use super::*; + + #[test] + fn test_output_prover() { + let mut rng = thread_rng(); + + let output = cl::OutputWitness { + note: cl::NoteWitness::basic(32, "NMO"), + balance_blinding: cl::BalanceWitness::random(&mut rng), + nf_pk: cl::NullifierSecret::random(&mut rng).commit(), + nonce: cl::NullifierNonce::random(&mut rng), + }; + + let mut proved_output = ProvedOutput::prove(&output); + + let expected_output_cm = output.commit(); + + assert_eq!(proved_output.output, expected_output_cm); + assert!(proved_output.verify()); + + let wrong_output_cms = [ + cl::Output { + note_comm: cl::NoteWitness::basic(100, "NMO").commit( + cl::NullifierSecret::random(&mut rng).commit(), + cl::NullifierNonce::random(&mut rng), + ), + ..expected_output_cm + }, + cl::Output { + note_comm: cl::NoteWitness::basic(100, "NMO").commit( + cl::NullifierSecret::random(&mut rng).commit(), + cl::NullifierNonce::random(&mut rng), + ), + balance: cl::BalanceWitness::random(&mut rng) + .commit(&cl::NoteWitness::basic(100, "NMO")), + }, + ]; + + for wrong_output_cm in wrong_output_cms { + proved_output.output = wrong_output_cm; + assert!(!proved_output.verify()); + } + } +} diff --git a/goas/cl/ledger/src/partial_tx.rs b/goas/cl/ledger/src/partial_tx.rs new file mode 100644 index 0000000..bd155f4 --- /dev/null +++ b/goas/cl/ledger/src/partial_tx.rs @@ -0,0 +1,26 @@ +use crate::{input::ProvedInput, output::ProvedOutput}; + +pub struct ProvedPartialTx { + pub inputs: Vec, + pub outputs: Vec, +} + +impl ProvedPartialTx { + pub fn prove( + ptx: &cl::PartialTxWitness, + note_commitments: &[cl::NoteCommitment], + ) -> ProvedPartialTx { + Self { + inputs: Vec::from_iter( + ptx.inputs + .iter() + .map(|i| ProvedInput::prove(i, note_commitments)), + ), + outputs: Vec::from_iter(ptx.outputs.iter().map(ProvedOutput::prove)), + } + } + + pub fn verify(&self) -> bool { + self.inputs.iter().all(ProvedInput::verify) && self.outputs.iter().all(ProvedOutput::verify) + } +} diff --git a/goas/cl/ledger/tests/simple_transfer.rs b/goas/cl/ledger/tests/simple_transfer.rs new file mode 100644 index 0000000..6bb8def --- /dev/null +++ b/goas/cl/ledger/tests/simple_transfer.rs @@ -0,0 +1,49 @@ +use ledger::{bundle::ProvedBundle, partial_tx::ProvedPartialTx}; +use rand_core::CryptoRngCore; + +fn receive_utxo( + note: cl::NoteWitness, + nf_pk: cl::NullifierCommitment, + rng: impl CryptoRngCore, +) -> cl::OutputWitness { + cl::OutputWitness::random(note, nf_pk, rng) +} + +#[test] +fn test_simple_transfer() { + let mut rng = rand::thread_rng(); + + let sender_nf_sk = cl::NullifierSecret::random(&mut rng); + let sender_nf_pk = sender_nf_sk.commit(); + + let recipient_nf_pk = cl::NullifierSecret::random(&mut rng).commit(); + + // Assume the sender has received an unspent output from somewhere + let utxo = receive_utxo(cl::NoteWitness::basic(10, "NMO"), sender_nf_pk, &mut rng); + + let note_commitments = vec![utxo.commit_note()]; + + let input = cl::InputWitness::random(utxo, sender_nf_sk, &mut rng); + + // and wants to send 8 NMO to some recipient and return 2 NMO to itself. + let recipient_output = + cl::OutputWitness::random(cl::NoteWitness::basic(8, "NMO"), recipient_nf_pk, &mut rng); + let change_output = + cl::OutputWitness::random(cl::NoteWitness::basic(2, "NMO"), sender_nf_pk, &mut rng); + + let ptx_witness = cl::PartialTxWitness { + inputs: vec![input], + outputs: vec![recipient_output, change_output], + }; + + let proved_ptx = ProvedPartialTx::prove(&ptx_witness, ¬e_commitments); + + assert!(proved_ptx.verify()); + + let bundle = cl::Bundle { + partials: vec![ptx_witness.commit()], + }; + + let proved_bundle = ProvedBundle::prove(&bundle, &ptx_witness.balance_blinding()); + assert!(proved_bundle.verify()); +} diff --git a/goas/cl/proof_statements/src/death_constraint.rs b/goas/cl/proof_statements/src/death_constraint.rs index efcac9e..019ecdb 100644 --- a/goas/cl/proof_statements/src/death_constraint.rs +++ b/goas/cl/proof_statements/src/death_constraint.rs @@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DeathConstraintPublic { - pub cm_root: [u8; 32], pub nf: Nullifier, pub ptx_root: PtxRoot, } diff --git a/goas/cl/proof_statements/src/lib.rs b/goas/cl/proof_statements/src/lib.rs index 8e58650..36287af 100644 --- a/goas/cl/proof_statements/src/lib.rs +++ b/goas/cl/proof_statements/src/lib.rs @@ -1,3 +1,4 @@ pub mod death_constraint; pub mod input; +pub mod partial_tx; pub mod ptx; diff --git a/goas/cl/proof_statements/src/ptx.rs b/goas/cl/proof_statements/src/ptx.rs index 2428e09..6cbc2a0 100644 --- a/goas/cl/proof_statements/src/ptx.rs +++ b/goas/cl/proof_statements/src/ptx.rs @@ -1,5 +1,6 @@ use cl::{merkle, InputWitness, OutputWitness, PtxRoot}; use serde::{Deserialize, Serialize}; + /// An input to a partial transaction #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PartialTxInputPrivate { diff --git a/goas/cl/risc0_proofs/Cargo.toml b/goas/cl/risc0_proofs/Cargo.toml index 198a3c0..7a77cd5 100644 --- a/goas/cl/risc0_proofs/Cargo.toml +++ b/goas/cl/risc0_proofs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["input"] +methods = ["input", "output", "bundle", "death_constraint_nop"] diff --git a/goas/cl/risc0_proofs/bundle/Cargo.toml b/goas/cl/risc0_proofs/bundle/Cargo.toml new file mode 100644 index 0000000..a2a0124 --- /dev/null +++ b/goas/cl/risc0_proofs/bundle/Cargo.toml @@ -0,0 +1,19 @@ +[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" } +proof_statements = { path = "../../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" } diff --git a/goas/cl/risc0_proofs/bundle/src/main.rs b/goas/cl/risc0_proofs/bundle/src/main.rs new file mode 100644 index 0000000..ac84364 --- /dev/null +++ b/goas/cl/risc0_proofs/bundle/src/main.rs @@ -0,0 +1,17 @@ +/// Bundle Proof +/// +/// The bundle proof demonstrates that the set of partial transactions +/// balance to zero. i.e. \sum inputs = \sum outputs. +/// +/// This is done by proving knowledge of some blinding factor `r` s.t. +/// \sum outputs - \sum input = 0*G + r*H +/// +/// To avoid doing costly ECC in stark, we compute only the RHS in stark. +/// The sums and equality is checked outside of stark during proof verification. +use risc0_zkvm::guest::env; + +fn main() { + let zero_witness: cl::BalanceWitness = env::read(); + let zero_balance = cl::Balance::zero(zero_witness); + env::commit(&zero_balance); +} diff --git a/goas/cl/risc0_proofs/death_constraint_nop/Cargo.toml b/goas/cl/risc0_proofs/death_constraint_nop/Cargo.toml new file mode 100644 index 0000000..46cab6a --- /dev/null +++ b/goas/cl/risc0_proofs/death_constraint_nop/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "death_constraint_nop" +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" } +proof_statements = { path = "../../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" } diff --git a/goas/cl/risc0_proofs/death_constraint_nop/src/main.rs b/goas/cl/risc0_proofs/death_constraint_nop/src/main.rs new file mode 100644 index 0000000..09920ad --- /dev/null +++ b/goas/cl/risc0_proofs/death_constraint_nop/src/main.rs @@ -0,0 +1,8 @@ +/// Death Constraint No-op Proof +use proof_statements::death_constraint::DeathConstraintPublic; +use risc0_zkvm::guest::env; + +fn main() { + let public: DeathConstraintPublic = env::read(); + env::commit(&public); +} diff --git a/goas/cl/risc0_proofs/output/Cargo.toml b/goas/cl/risc0_proofs/output/Cargo.toml new file mode 100644 index 0000000..6b4f0f9 --- /dev/null +++ b/goas/cl/risc0_proofs/output/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "output" +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" } +proof_statements = { path = "../../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" } diff --git a/goas/cl/risc0_proofs/output/src/main.rs b/goas/cl/risc0_proofs/output/src/main.rs new file mode 100644 index 0000000..33b5ee0 --- /dev/null +++ b/goas/cl/risc0_proofs/output/src/main.rs @@ -0,0 +1,12 @@ +/// Output Proof +/// +/// given randomness `r` and `note=(value, unit, ...)` prove that +/// - balance = balance_commit(value, unit, r) +/// - note_cm = note_commit(note) +use risc0_zkvm::guest::env; + +fn main() { + let output: cl::OutputWitness = env::read(); + let output_cm = output.commit(); + env::commit(&output_cm); +}