From 507e1627d72bea5368d8fe1992106399f02c7d3a Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Thu, 20 Jun 2024 19:04:31 +0200 Subject: [PATCH] refactor risc0 zone --- goas/host/Cargo.toml | 2 + goas/host/src/main.rs | 74 ++++++++++-------- goas/methods/guest/Cargo.toml | 4 +- goas/methods/guest/src/main.rs | 133 ++++++++++++++++++++++----------- 4 files changed, 139 insertions(+), 74 deletions(-) diff --git a/goas/host/Cargo.toml b/goas/host/Cargo.toml index 246ebe9..3527033 100644 --- a/goas/host/Cargo.toml +++ b/goas/host/Cargo.toml @@ -8,3 +8,5 @@ methods = { path = "../methods" } risc0-zkvm = { version = "1.0.1" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } serde = "1.0" +blake2 = "0.10" +bincode = "1" \ No newline at end of file diff --git a/goas/host/src/main.rs b/goas/host/src/main.rs index 4905c94..f555dc8 100644 --- a/goas/host/src/main.rs +++ b/goas/host/src/main.rs @@ -1,49 +1,60 @@ // 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 blake2::{Blake2s256, Digest}; use methods::{METHOD_ELF, METHOD_ID}; use risc0_zkvm::{default_prover, ExecutorEnv}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -type Note = BTreeMap; + +// state of the zone +type State = BTreeMap; +// list of all inputs that were executed up to this point +type Journal = Vec; + +#[derive(Clone, Serialize, Deserialize)] +struct Note { + state_cm: [u8; 32], + journal_cm: [u8; 32], + zone_input: Input, +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +enum Input { + Transfer { from: u32, to: u32, amount: u32 }, +} + 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(); - let in_note: BTreeMap = [(0, 1000)].into_iter().collect(); - let in_note_cm = calculate_note_hash(&in_note); + let state: BTreeMap = [(0, 1000)].into_iter().collect(); + let journal = vec![]; - let out_note: BTreeMap = [(0, 990), (1, 10)].into_iter().collect(); - let out_note_cm = calculate_note_hash(&out_note); + let note = Note { + state_cm: calculate_state_hash(&state), + journal_cm: calculate_journal_hash(&journal), + zone_input: Input::Transfer { + from: 0, + to: 1, + amount: 10, + }, + }; let ptx_root = [0u8; 32]; let in_ptx_path: Vec<[u8; 32]> = vec![[0; 32]]; - let out_ptx_path: Vec<[u8; 32]> = vec![[0; 32]]; - - println!("Before: {:?}", in_note); - - let from = 0; - let to = 1; - let amount: u32 = 10; let env = ExecutorEnv::builder() .write(&ptx_root) .unwrap() .write(&in_ptx_path) .unwrap() - .write(&out_ptx_path) + .write(¬e) .unwrap() - .write(&in_note_cm) + .write(&state) .unwrap() - .write(&out_note_cm) - .unwrap() - .write(&from) - .unwrap() - .write(&to) - .unwrap() - .write(&amount) - .unwrap() - .write(&in_note) + .write(&journal) .unwrap() .build() .unwrap(); @@ -53,8 +64,7 @@ fn main() { // Proof information by proving the specified ELF binary. // This struct contains the receipt along with statistics about execution of the guest - let opts = - risc0_zkvm::ProverOpts::default().with_receipt_kind(risc0_zkvm::ReceiptKind::Groth16); + let opts = risc0_zkvm::ProverOpts::default(); let prove_info = prover.prove_with_opts(env, METHOD_ELF, &opts).unwrap(); // extract the receipt. @@ -63,7 +73,7 @@ fn main() { // TODO: Implement code for retrieving receipt journal here. // For example: - // let output: BTreeMap = receipt.journal.decode().unwrap(); + let (state_cm, journal_cm): ([u8; 32], [u8; 32]) = receipt.journal.decode().unwrap(); // println!("After: {:?}", output); // The receipt was verified at the end of proving, but the below code is an @@ -71,8 +81,12 @@ fn main() { receipt.verify(METHOD_ID).unwrap(); } -fn calculate_note_hash(n: &Note) -> [u8; 32] { - let mut out = [0u8; 32]; - out[0] = n.len() as u8; - out +fn calculate_state_hash(state: &State) -> [u8; 32] { + let bytes = bincode::serialize(state).unwrap(); + Blake2s256::digest(&bytes).into() +} + +fn calculate_journal_hash(journal: &Journal) -> [u8; 32] { + let bytes = bincode::serialize(journal).unwrap(); + Blake2s256::digest(&bytes).into() } diff --git a/goas/methods/guest/Cargo.toml b/goas/methods/guest/Cargo.toml index 0db3541..33dab58 100644 --- a/goas/methods/guest/Cargo.toml +++ b/goas/methods/guest/Cargo.toml @@ -7,4 +7,6 @@ edition = "2021" [dependencies] risc0-zkvm = { version = "1.0.1", default-features = false, features = ['std'] } -blake2 = "0.10" \ No newline at end of file +blake2 = "0.10" +serde = { version = "1.0", features = ["derive"] } +bincode = "1" \ No newline at end of file diff --git a/goas/methods/guest/src/main.rs b/goas/methods/guest/src/main.rs index 297a782..68f09a2 100644 --- a/goas/methods/guest/src/main.rs +++ b/goas/methods/guest/src/main.rs @@ -1,61 +1,108 @@ +use blake2::{Blake2s256, Digest}; use risc0_zkvm::guest::env; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; /// Public Inputs: /// * ptx_root: the root of the partial tx merkle tree of inputs/outputs - -/// * in_note_cm: a commitment to the input note -/// * out_note_cm: a commitment to the output note /// Private inputs: -/// * in_note: a note corresponding to the input commitment -/// * in_ptx_path: the path from a leaf containing the input state commitment in the ptx -/// * out_ptx_path: the path from a leaf containing the output state commitment in the ptx -/// * from: u32, the account to transfer from -/// * to: u32, the account to transfer to -/// * amount: u32, the amount to transfer -/// -type Note = BTreeMap; +/// TODO + +// state of the zone +type State = BTreeMap; +// list of all inputs that were executed up to this point +type Journal = Vec; + +#[derive(Clone, Serialize, Deserialize)] +struct Note { + state_cm: [u8; 32], + journal_cm: [u8; 32], + zone_input: Input, +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +enum Input { + Transfer { from: u32, to: u32, amount: u32 }, +} + +/// State transition function +fn stf(mut state: State, input: Input) -> State { + match input { + Input::Transfer { from, to, amount } => { + // compute transfer + let from = state.entry(from).or_insert(0); + *from = from.checked_sub(amount).unwrap(); + *state.entry(to).or_insert(0) += amount; + } + } + state +} + +/// Glue the zone and the cl together, specifically, it verifies the note requesting +/// a transfer is included as part of the same transaction in the cl +fn verify_ptx_inputs(ptx_root: [u8; 32], ptx_path: &[[u8; 32]], note: &Note) { + assert!(verify_path(&ptx_root, &ptx_path, ¬e)); +} + +/// Glue the zone and the cl together, specifically, it verifies an output note +/// containing the zone state is included as part of the same transaction in the cl +/// (this is done in the death condition to disallow burning) +fn verify_ptx_output(ptx_root: [u8; 32], ptx_path: &[[u8; 32]], note: &Note) { + assert!(verify_path(&ptx_root, &ptx_path, ¬e)); +} + +fn execute( + ptx_root: [u8; 32], + ptx_path: Vec<[u8; 32]>, + note: Note, + state: State, + mut journal: Journal, +) -> (State, Journal) { + // verify ptx/cl preconditions + verify_ptx_inputs(ptx_root, &ptx_path, ¬e); + + // check the commitments match the actual data + let state_cm = calculate_state_hash(&state); + let journal_cm = calculate_journal_hash(&journal); + assert_eq!(state_cm, note.state_cm); + assert_eq!(journal_cm, note.journal_cm); + + // then run the state transition function + let input = note.zone_input; + let state = stf(state, input); + journal.push(input); + + let state_cm = calculate_state_hash(&state); + let journal_cm = calculate_journal_hash(&journal); + + // verifying ptx/cl postconditions + verify_ptx_outputs(ptx_root, &ptx_path, out_note); + // output the new state and the execution receipt + (state, journal) +} fn main() { // public input let ptx_root: [u8; 32] = env::read(); - let in_ptx_path: Vec<[u8; 32]> = env::read(); - let out_ptx_path: Vec<[u8; 32]> = env::read(); - - let in_note_cm: [u8; 32] = env::read(); - let out_note_cm: [u8; 32] = env::read(); - - let from: u32 = env::read(); - let to: u32 = env::read(); - let amount: u32 = env::read(); // private input - let in_note: Note = env::read(); + let in_ptx_path: Vec<[u8; 32]> = env::read(); + let out_ptx_path: Vec<[u8; 32]> = env::read(); + let note: Note = env::read(); + let state: State = env::read(); + let journal: Journal = env::read(); - // check the note is consistent with the state commitment and is part of the path - assert_eq!(in_note_cm, calculate_note_hash(&in_note)); - // verify the input state commitment is part of the partial tx - assert!(verify_path(&ptx_root, &in_ptx_path, &in_note)); - - // the note is just the state - let mut state = in_note.clone(); - - // compute transfer - let from = state.entry(from).or_insert(0); - - *from = from.checked_sub(amount).unwrap(); - *state.entry(to).or_insert(0) += amount; - - // check that the new state is consistent with the output state commitment and it's part of the output - assert_eq!(calculate_note_hash(&state), out_note_cm); - let out_note = state; - assert!(verify_path(&ptx_root, &out_ptx_path, &out_note)); + execute(ptx_root, in_ptx_path, out_ptx_path, note, state, journal); } -fn calculate_note_hash(n: &Note) -> [u8; 32] { - let mut out = [0u8; 32]; - out[0] = n.len() as u8; - out +fn calculate_state_hash(state: &State) -> [u8; 32] { + let bytes = bincode::serialize(state).unwrap(); + Blake2s256::digest(&bytes).into() +} + +fn calculate_journal_hash(journal: &Journal) -> [u8; 32] { + let bytes = bincode::serialize(journal).unwrap(); + Blake2s256::digest(&bytes).into() } fn verify_path(_ptx_root: &[u8; 32], _ptx_path: &[[u8; 32]], _note: &Note) -> bool {