From 657ec4480f9b6d9c839eaaca4fe6011b00b46768 Mon Sep 17 00:00:00 2001 From: Victor Dumitrescu Date: Fri, 13 Dec 2024 10:29:29 +0100 Subject: [PATCH 1/4] RISC-V: Adapt ProofGen managers trait bounds --- src/riscv/lib/src/state_backend/proof_backend.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/riscv/lib/src/state_backend/proof_backend.rs b/src/riscv/lib/src/state_backend/proof_backend.rs index 725adb40995f..36213b8e7a5f 100644 --- a/src/riscv/lib/src/state_backend/proof_backend.rs +++ b/src/riscv/lib/src/state_backend/proof_backend.rs @@ -148,7 +148,7 @@ impl ManagerRead for ProofGen { /// Implementation of [`ManagerWrite`] which wraps another manager and /// records written locations but does not write to the wrapped region directly. -impl ManagerWrite for ProofGen { +impl ManagerWrite for ProofGen { fn region_write( region: &mut Self::Region, index: usize, @@ -211,7 +211,7 @@ impl ManagerWrite for ProofGen { /// Implementation of [`ManagerReadWrite`] which wraps another manager and /// additionally records read and written locations. -impl ManagerReadWrite for ProofGen { +impl ManagerReadWrite for ProofGen { fn region_replace( region: &mut Self::Region, index: usize, -- GitLab From f363b7294a24ce59d11985b9fe1fe7bb6b72c812 Mon Sep 17 00:00:00 2001 From: Victor Dumitrescu Date: Fri, 13 Dec 2024 10:57:27 +0100 Subject: [PATCH 2/4] RISC-V: Adapt Merkleise implementations --- .../lib/src/machine_state/main_memory.rs | 10 ++++--- .../lib/src/state_backend/proof_backend.rs | 28 +++++++++++-------- src/riscv/lib/src/state_backend/region.rs | 21 ++++++++------ 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/riscv/lib/src/machine_state/main_memory.rs b/src/riscv/lib/src/machine_state/main_memory.rs index d5256a6e99c3..fdf83d63f9d7 100644 --- a/src/riscv/lib/src/machine_state/main_memory.rs +++ b/src/riscv/lib/src/machine_state/main_memory.rs @@ -12,7 +12,7 @@ use crate::{ merkle::{MerkleTree, Merkleisable}, ProofGen, }, - verify_backend, ManagerDeserialise, ManagerRead, ManagerSerialise, + verify_backend, ManagerDeserialise, ManagerRead, ManagerSerialise, Ref, }, storage::binary, }; @@ -129,7 +129,7 @@ pub trait MainMemoryLayout: backend::ProofLayout + 'static { fn hash_data(data: &Self::Data) -> Result; fn to_merkle_tree( - data: &Self::Data>, + data: &Self::Data>>, ) -> Result; } @@ -203,7 +203,7 @@ impl MainMemoryLayout for Sizes { } fn to_merkle_tree( - data: &Self::Data>, + data: &Self::Data>>, ) -> Result { data.to_merkle_tree() } @@ -394,7 +394,9 @@ impl RootHashable for MainMemory { } } -impl Merkleisable for MainMemory> { +impl Merkleisable + for MainMemory>> +{ fn to_merkle_tree(&self) -> Result { L::to_merkle_tree(&self.data) } diff --git a/src/riscv/lib/src/state_backend/proof_backend.rs b/src/riscv/lib/src/state_backend/proof_backend.rs index 36213b8e7a5f..03d9e8bc620d 100644 --- a/src/riscv/lib/src/state_backend/proof_backend.rs +++ b/src/riscv/lib/src/state_backend/proof_backend.rs @@ -425,7 +425,7 @@ mod tests { owned_backend::Owned, proof_backend::merkle::Merkleisable, region::{DynCells, MERKLE_LEAF_SIZE}, - Cells, EnrichedCell, + Cells, EnrichedCell, FnManagerIdent, }; use proptest::{array, prop_assert_eq, proptest}; use std::collections::VecDeque; @@ -499,7 +499,10 @@ mod tests { let initial_root_hash = cells.hash().unwrap(); cells.write(i, value_after); prop_assert_eq!(cells.hash().unwrap(), initial_root_hash); - let merkle_tree = cells.to_merkle_tree().unwrap(); + let merkle_tree = cells.struct_ref::() + .to_merkle_tree() + .unwrap(); + match merkle_tree { MerkleTree::Leaf(hash, access_info, _) => { prop_assert_eq!(hash, initial_root_hash); @@ -529,8 +532,7 @@ mod tests { bytes_after: [u8; ELEM_SIZE], write_address in &address_range)| { let cells = Box::new([byte_before; DYN_REGION_SIZE]); - let dyn_region: ProofDynRegion = - ProofDynRegion::bind(cells); + let dyn_region: ProofDynRegion = ProofDynRegion::bind(cells); let mut dyn_cells: DynCells> = DynCells::bind(dyn_region); @@ -545,8 +547,7 @@ mod tests { assert_eq!(value, value_after); let cells = Box::new([byte_before; DYN_REGION_SIZE]); - let dyn_region: ProofDynRegion = - ProofDynRegion::bind(cells); + let dyn_region: ProofDynRegion = ProofDynRegion::bind(cells); let mut dyn_cells: DynCells> = DynCells::bind(dyn_region); @@ -567,8 +568,7 @@ mod tests { assert_eq!(value, value_after); let cells = Box::new([byte_before; DYN_REGION_SIZE]); - let dyn_region: ProofDynRegion = - ProofDynRegion::bind(cells); + let dyn_region: ProofDynRegion = ProofDynRegion::bind(cells); let mut dyn_cells: DynCells> = DynCells::bind(dyn_region); @@ -607,8 +607,11 @@ mod tests { // Build the Merkle tree and check that it has the root hash of the // initial wrapped region. - let merkle_tree = dyn_cells.to_merkle_tree().unwrap(); - let cells: DynCells = DynCells::bind(region); + let merkle_tree = dyn_cells + .struct_ref::() + .to_merkle_tree() + .unwrap(); + let cells: DynCells = DynCells::bind(region); let initial_root_hash = cells.hash().unwrap(); prop_assert_eq!(merkle_tree.root_hash(), initial_root_hash); @@ -698,7 +701,10 @@ mod tests { let initial_root_hash = proof_cell.hash().unwrap(); proof_cell.write(value_after); prop_assert_eq!(proof_cell.hash().unwrap(), initial_root_hash); - let merkle_tree = proof_cell.to_merkle_tree().unwrap(); + let merkle_tree = proof_cell + .struct_ref::() + .to_merkle_tree() + .unwrap(); match merkle_tree { MerkleTree::Leaf(hash, access_info, _) => { prop_assert_eq!(hash, initial_root_hash); diff --git a/src/riscv/lib/src/state_backend/region.rs b/src/riscv/lib/src/state_backend/region.rs index 570f49b436e6..77812c0d3c61 100644 --- a/src/riscv/lib/src/state_backend/region.rs +++ b/src/riscv/lib/src/state_backend/region.rs @@ -198,13 +198,15 @@ impl + Copy, B: Copy, M: ManagerRead, N: ManagerRead> PartialEq< } } -impl Merkleisable for Cell> { +impl Merkleisable for Cell>> { fn to_merkle_tree(&self) -> Result { self.region.to_merkle_tree() } } -impl AccessInfoAggregatable for Cell> { +impl AccessInfoAggregatable + for Cell>> +{ fn aggregate_access_info(&self) -> AccessInfo { self.region.region.get_access_info() } @@ -416,17 +418,18 @@ where } } -impl Merkleisable for EnrichedCell> +impl Merkleisable for EnrichedCell>> where V::E: serde::Serialize, { fn to_merkle_tree(&self) -> Result { - let serialised = ProofEnrichedCell::serialise_inner_enriched_cell(&self.cell)?; + let serialised = ProofEnrichedCell::serialise_inner_enriched_cell(self.cell)?; MerkleTree::make_merkle_leaf(serialised, self.cell.get_access_info()) } } -impl AccessInfoAggregatable for EnrichedCell> +impl AccessInfoAggregatable + for EnrichedCell>> where V::E: serde::Serialize, { @@ -476,7 +479,7 @@ impl + Copy, B: Copy, const LEN: usize, M: ManagerRead, N: Manag } impl Merkleisable - for Cells> + for Cells>> { fn to_merkle_tree(&self) -> Result { // RV-282: Break down into multiple leaves if the size of the `Cells` @@ -487,7 +490,7 @@ impl Merkleisable } impl AccessInfoAggregatable - for Cells> + for Cells>> { fn aggregate_access_info(&self) -> AccessInfo { self.region.get_access_info() @@ -634,7 +637,7 @@ impl RootHashable for DynCells { } } -impl Merkleisable for DynCells> { +impl Merkleisable for DynCells>> { fn to_merkle_tree(&self) -> Result { let mut writer = MerkleWriter::new( MERKLE_LEAF_SIZE, @@ -644,7 +647,7 @@ impl Merkleisable for DynCells [u8; MERKLE_LEAF_SIZE.get()] { - ProofDynRegion::inner_dyn_region_read(&self.region, address) + ProofDynRegion::inner_dyn_region_read(self.region, address) }; chunks_to_writer::(&mut writer, read)?; writer.finalise() -- GitLab From e14a2ee58aefa1d91126bf291b84f702246c43c9 Mon Sep 17 00:00:00 2001 From: Victor Dumitrescu Date: Thu, 19 Dec 2024 08:57:27 +0100 Subject: [PATCH 3/4] RISC-V: Proof exposes initial state hash --- .../src/state_backend/proof_backend/merkle.rs | 4 +++ .../src/state_backend/proof_backend/proof.rs | 34 +++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/riscv/lib/src/state_backend/proof_backend/merkle.rs b/src/riscv/lib/src/state_backend/proof_backend/merkle.rs index 243b35635168..88abae1465b7 100644 --- a/src/riscv/lib/src/state_backend/proof_backend/merkle.rs +++ b/src/riscv/lib/src/state_backend/proof_backend/merkle.rs @@ -754,6 +754,10 @@ mod tests { assert_eq!(compressed_merkle_proof, proof); assert_eq!(merkle_tree.to_merkle_proof().unwrap(), proof); + assert_eq!( + compressed_merkle_proof.hash().unwrap(), + merkle_tree_root_hash + ); Ok(()) }; diff --git a/src/riscv/lib/src/state_backend/proof_backend/proof.rs b/src/riscv/lib/src/state_backend/proof_backend/proof.rs index 876ad19fe080..46699db9d54e 100644 --- a/src/riscv/lib/src/state_backend/proof_backend/proof.rs +++ b/src/riscv/lib/src/state_backend/proof_backend/proof.rs @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2024 TriliTech +// SPDX-FileCopyrightText: 2024 Nomadic Labs // // SPDX-License-Identifier: MIT @@ -13,8 +14,11 @@ //! //! - Convert [`MerkleTree`] to [`MerkleProof`] -use super::tree::{ModifyResult, Tree}; -use crate::{state_backend::hash::Hash, storage::DIGEST_SIZE}; +use super::tree::{impl_modify_map_collect, ModifyResult, Tree}; +use crate::{ + state_backend::hash::{Hash, RootHashable}, + storage::{HashError, DIGEST_SIZE}, +}; use itertools::Itertools; /// Structure of a proof transitioning from state A to state B. @@ -45,6 +49,13 @@ impl Proof { &self.partial_tree } + /// Get the initial state hash of the proof. + pub fn initial_state_hash(&self) -> Hash { + self.partial_tree + .hash() + .expect("Computing the root hash of the Merkle proof should not fail") + } + /// Get the final state hash of the proof. pub fn final_state_hash(&self) -> &Hash { &self.final_state_hash @@ -99,6 +110,25 @@ impl MerkleProof { } } +impl RootHashable for MerkleProof { + fn hash(&self) -> Result { + impl_modify_map_collect( + self, + |subtree| { + Ok(match subtree { + Tree::Node(vec) => ModifyResult::NodeContinue((), vec.iter().collect()), + Tree::Leaf(data) => ModifyResult::LeafStop(data), + }) + }, + |leaf| match leaf { + MerkleProofLeaf::Blind(hash) => Ok(*hash), + MerkleProofLeaf::Read(data) => Hash::blake2b_hash_bytes(data.as_slice()), + }, + |(), leaves| leaves.hash(), + ) + } +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum Tag { Node, -- GitLab From 9aeb5e5c1d23ac3265e3da1b76c0aaf3b9f55c70 Mon Sep 17 00:00:00 2001 From: Victor Dumitrescu Date: Wed, 18 Dec 2024 09:30:48 +0100 Subject: [PATCH 4/4] RISC-V: Stepper can produce proof --- src/riscv/lib/src/pvm/common.rs | 35 +++++++++++++- src/riscv/lib/src/stepper/pvm.rs | 62 ++++++++++++++++++++++--- src/riscv/lib/tests/common/mod.rs | 6 +-- src/riscv/lib/tests/test_determinism.rs | 2 +- src/riscv/lib/tests/test_proofs.rs | 36 +++++++++++--- 5 files changed, 122 insertions(+), 19 deletions(-) diff --git a/src/riscv/lib/src/pvm/common.rs b/src/riscv/lib/src/pvm/common.rs index f76a036db2b1..d53490f0a698 100644 --- a/src/riscv/lib/src/pvm/common.rs +++ b/src/riscv/lib/src/pvm/common.rs @@ -6,7 +6,11 @@ use crate::{ default::ConstDefault, machine_state::{self, main_memory}, pvm::sbi, - state_backend::{self, Atom, Cell}, + state_backend::{ + self, + proof_backend::{ProofDynRegion, ProofEnrichedCell, ProofGen, ProofRegion}, + Atom, Cell, + }, traps::EnvironException, }; use std::{ @@ -134,6 +138,35 @@ impl< ) } + /// Generate a proof-generating version of this PVM. + pub fn start_proof(&self) -> Pvm>> { + enum ProofWrapper {} + + impl state_backend::FnManager for ProofWrapper { + type Output = ProofGen; + + fn map_region( + input: ::Region, + ) -> as state_backend::ManagerBase>::Region { + ProofRegion::bind(input) + } + + fn map_dyn_region( + input: ::DynRegion, + ) -> as state_backend::ManagerBase>::DynRegion { + ProofDynRegion::bind(input) + } + + fn map_enriched_cell( + input: ::EnrichedCell, + ) -> as state_backend::ManagerBase>::EnrichedCell { + ProofEnrichedCell::bind(input) + } + } + + Pvm::bind(self.struct_ref::()) + } + /// Reset the PVM state. pub fn reset(&mut self) where diff --git a/src/riscv/lib/src/stepper/pvm.rs b/src/riscv/lib/src/stepper/pvm.rs index 59f511ea95b6..8c2bf0e9106c 100644 --- a/src/riscv/lib/src/stepper/pvm.rs +++ b/src/riscv/lib/src/stepper/pvm.rs @@ -1,8 +1,10 @@ // SPDX-FileCopyrightText: 2024 TriliTech +// SPDX-FileCopyrightText: 2024 Nomadic Labs // // SPDX-License-Identifier: MIT use super::{Stepper, StepperStatus}; +use crate::state_backend::{ManagerBase, ManagerReadWrite}; use crate::{ kernel_loader, machine_state::{ @@ -16,7 +18,7 @@ use crate::{ state_backend::{ hash::{Hash, RootHashable}, owned_backend::Owned, - proof_backend::proof::Proof, + proof_backend::{merkle::Merkleisable, proof::Proof, ProofGen}, verify_backend::Verifier, AllocatedOf, FnManagerIdent, ProofLayout, ProofTree, Ref, }, @@ -37,8 +39,13 @@ pub enum PvmStepperError { } /// Wrapper over a PVM that lets you step through it -pub struct PvmStepper<'hooks, ML: MainMemoryLayout = M1G, CL: CacheLayouts = DefaultCacheLayouts> { - pvm: Pvm, +pub struct PvmStepper< + 'hooks, + ML: MainMemoryLayout = M1G, + CL: CacheLayouts = DefaultCacheLayouts, + M: ManagerBase = Owned, +> { + pvm: Pvm, hooks: PvmHooks<'hooks>, inbox: Inbox, rollup_address: [u8; 20], @@ -135,10 +142,16 @@ impl<'hooks, ML: MainMemoryLayout, CL: CacheLayouts> PvmStepper<'hooks, ML, CL> RootHashable::hash(&refs).unwrap() } - /// Produce the Merkle proof for evaluating one step on the given PVM state. - pub fn produce_proof(&mut self) -> Proof { - // TODO RV-338 PVM stepper can produce a proof - todo!() + /// Create a new stepper in which the existing PVM is managed by + /// the proof-generating backend. + pub fn start_proof_mode<'a>(&'a self) -> PvmStepper<'hooks, ML, CL, ProofGen>> { + PvmStepper::<'hooks, ML, CL, ProofGen>> { + pvm: self.pvm.start_proof(), + hooks: PvmHooks::none(), + inbox: self.inbox.clone(), + rollup_address: self.rollup_address, + origination_level: self.origination_level, + } } /// Verify a Merkle proof. The [`PvmStepper`] is used for inbox information. @@ -191,6 +204,41 @@ impl<'hooks, ML: MainMemoryLayout, CL: CacheLayouts> PvmStepper<'hooks, ML, CL> } } +impl<'hooks, ML: MainMemoryLayout, CL: CacheLayouts, M: ManagerReadWrite> + PvmStepper<'hooks, ML, CL, M> +{ + /// Perform one evaluation step. + pub fn eval_one(&mut self) { + self.pvm.eval_one(&mut self.hooks) + } +} + +impl<'hooks, ML: MainMemoryLayout, CL: CacheLayouts> PvmStepper<'hooks, ML, CL> +where + for<'a, 'b> AllocatedOf, Ref<'a, ProofGen>>>: Merkleisable, + for<'a> AllocatedOf, Ref<'a, Owned>>: RootHashable, +{ + /// Produce the Merkle proof for evaluating one step on the given PVM state. + /// The given stepper takes one step. + pub fn produce_proof(&mut self) -> Option { + // Step using the proof mode stepper in order to obtain the proof + let mut proof_stepper = self.start_proof_mode(); + proof_stepper.eval_one(); + let merkle_proof = proof_stepper + .pvm + .struct_ref::() + .to_merkle_tree() + .expect("Obtaining the Merkle tree of a proof-gen state should not fail") + .to_merkle_proof() + .expect("Converting a Merkle tree to a compressed Merkle proof should not fail"); + + // TODO RV-373 : Proof-generating backend should also compute final state hash + // Currently, the proof and the initial state hash are valid, + // but the final state hash is not. + Some(Proof::new(merkle_proof, self.hash())) + } +} + impl<'hooks, ML: MainMemoryLayout, CL: CacheLayouts> Stepper for PvmStepper<'hooks, ML, CL> { type MainMemoryLayout = ML; diff --git a/src/riscv/lib/tests/common/mod.rs b/src/riscv/lib/tests/common/mod.rs index be2912915c8b..30a4a8a85368 100644 --- a/src/riscv/lib/tests/common/mod.rs +++ b/src/riscv/lib/tests/common/mod.rs @@ -40,15 +40,15 @@ pub fn make_stepper_factory() -> impl Fn() -> PvmStepper<'static, M100M, Default } } -pub fn dissect_steps(mut max_steps: usize) -> Vec { +pub fn dissect_steps(mut max_steps: usize, min_step_size: usize) -> Vec { let mut rng = rand::thread_rng(); let mut steps: Vec = std::iter::from_fn(|| { if max_steps == 0 { return None; } - let steps = max_steps.div_euclid(2).max(1); - let steps = rng.gen_range(0..=steps); + let steps = max_steps.div_euclid(2).max(min_step_size + 1); + let steps = rng.gen_range(min_step_size..=steps); max_steps = max_steps.saturating_sub(steps); diff --git a/src/riscv/lib/tests/test_determinism.rs b/src/riscv/lib/tests/test_determinism.rs index b9bf80d63a9b..d51e6b5e70cb 100644 --- a/src/riscv/lib/tests/test_determinism.rs +++ b/src/riscv/lib/tests/test_determinism.rs @@ -37,7 +37,7 @@ fn test_jstz_determinism() { let base_refs = base_stepper.struct_ref(); // Create multiple series of bisections that we will evaluate. - let ladder = dissect_steps(steps); + let ladder = dissect_steps(steps, 0); run_steps_ladder(&make_stepper, &ladder, &base_refs, base_hash); } diff --git a/src/riscv/lib/tests/test_proofs.rs b/src/riscv/lib/tests/test_proofs.rs index b0c9e7e8e324..ee21b993efbb 100644 --- a/src/riscv/lib/tests/test_proofs.rs +++ b/src/riscv/lib/tests/test_proofs.rs @@ -25,7 +25,9 @@ fn test_jstz_proofs() { let steps = base_result.steps(); let base_hash = base_stepper.hash(); - let ladder = dissect_steps(steps); + // For each step `s`, the stepper will initially step `s-1` steps, then + // produce a proof of the `s` step. The minimum step size thus needs to be 1. + let ladder = dissect_steps(steps, 1); run_steps_ladder(&make_stepper, &ladder, base_hash); } @@ -38,19 +40,39 @@ where let mut steps_done = 0; for &steps in ladder { + // Run one step short of `steps`, then produce a proof of the following step. + let steps = steps.checked_sub(1).expect("minimum step size is 1"); eprintln!("> Running {} steps ...", steps); - let StepperStatus::Running { .. } = stepper.step_max(Bound::Included(steps)) else { - panic!("Unexpected stepper result") - }; + let result = stepper.step_max(Bound::Included(steps)); steps_done += steps; + if steps_done != expected_steps { + assert!(matches!(result, StepperStatus::Running { .. })); + + eprintln!("> Producing proof"); + let proof = stepper.produce_proof().unwrap(); + + assert_eq!(proof.initial_state_hash(), stepper.hash()); + + // Run one final step, which is the step proven by `proof`, and check that its + // state hash matches the final state hash of `proof`. + eprintln!("> Running 1 step ..."); + stepper.eval_one(); + steps_done += 1; + // TODO RV-373 : Proof-generating backend should also compute final state hash + assert_eq!(proof.final_state_hash(), &stepper.hash()); + + assert!(stepper.verify_proof(proof)) + } else { + // Can't generate a proof on the next step if execution has ended + assert!(matches!(result, StepperStatus::Exited { .. })); + assert!(ladder.is_empty()) + }; + eprintln!( "> Done {:.2}%", (steps_done as f64 / expected_steps as f64) * 100.0 ); - - let proof = stepper.produce_proof(); - assert!(stepper.verify_proof(proof)) } assert_eq!(stepper.hash(), expected_hash); -- GitLab