Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Validity Conditions #418

Merged
merged 12 commits into from
Jun 20, 2023
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ rocksdb = { version = "0.19.0", features = ["lz4"] }
serde = { version = "1.0.137", features = ["derive", "rc"] }
serde_json = { version = "1.0" }
sha2 = "0.10.6"
digest = "0.10.6"
thiserror = "1.0.38"
tiny-keccak = "2.0.2"
tracing = "0.1.37"
Expand Down
1 change: 1 addition & 0 deletions adapters/celestia/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ serde = { workspace = true }
serde_cbor = "0.11.2"
serde_json = { workspace = true }
tokio = { version = "1", features = ["full"], optional = true }
thiserror = { workspace = true }
tracing = "0.1.37"

sov-rollup-interface = { path = "../../rollup-interface" }
Expand Down
6 changes: 0 additions & 6 deletions adapters/celestia/src/celestia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ pub struct MarshalledDataAvailabilityHeader {
pub column_roots: Vec<String>,
}

#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
pub struct PartialBlockId {
pub hash: ProtobufHash,
pub part_set_header: Vec<u8>,
}

/// A partially serialized tendermint header. Only fields which are actually inspected by
/// Jupiter are included in their raw form. Other fields are pre-encoded as protobufs.
///
Expand Down
40 changes: 36 additions & 4 deletions adapters/celestia/src/verifier/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use nmt_rs::NamespaceId;
use serde::{Deserialize, Serialize};
use sov_rollup_interface::{
crypto::SimpleHasher,
da::{self, BlobTransactionTrait, BlockHashTrait as BlockHash, CountedBufReader, DaSpec},
traits::{BlockHeaderTrait, CanonicalHash},
zk::traits::ValidityCondition,
Buf,
};
use thiserror::Error;

pub mod address;
pub mod proofs;
Expand Down Expand Up @@ -102,33 +106,61 @@ pub struct RollupParams {
pub namespace: NamespaceId,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
/// A validity condition expressing that a chain of DA layer blocks is contiguous and canonical
pub struct ChainValidityCondition {
pub prev_hash: [u8; 32],
pub block_hash: [u8; 32],
}
#[derive(Error, Debug)]
pub enum ValidityConditionError {
#[error("conditions for validity can only be combined if the blocks are consecutive")]
BlocksNotConsecutive,
}

impl ValidityCondition for ChainValidityCondition {
type Error = ValidityConditionError;
fn combine<H: SimpleHasher>(&self, rhs: Self) -> Result<Self, Self::Error> {
if self.block_hash != rhs.prev_hash {
return Err(ValidityConditionError::BlocksNotConsecutive);
}
Ok(rhs)
}
}

impl da::DaVerifier for CelestiaVerifier {
type Spec = CelestiaSpec;

type Error = ValidationError;

type ValidityCondition = ChainValidityCondition;

fn new(params: <Self::Spec as DaSpec>::ChainParams) -> Self {
Self {
rollup_namespace: params.namespace,
}
}

fn verify_relevant_tx_list(
fn verify_relevant_tx_list<H: SimpleHasher>(
&self,
block_header: &<Self::Spec as DaSpec>::BlockHeader,
txs: &[<Self::Spec as DaSpec>::BlobTransaction],
inclusion_proof: <Self::Spec as DaSpec>::InclusionMultiProof,
completeness_proof: <Self::Spec as DaSpec>::CompletenessProof,
) -> Result<(), Self::Error> {
) -> Result<Self::ValidityCondition, Self::Error> {
// Validate that the provided DAH is well-formed
block_header.validate_dah()?;
let validity_condition = ChainValidityCondition {
prev_hash: *block_header.prev_hash().inner(),
block_hash: *block_header.hash().inner(),
};

// Check the validity and completeness of the rollup row proofs, against the DAH.
// Extract the data from the row proofs and build a namespace_group from it
let rollup_shares_u8 = self.verify_row_proofs(completeness_proof, &block_header.dah)?;
if rollup_shares_u8.is_empty() {
if txs.is_empty() {
return Ok(());
return Ok(validity_condition);
}
return Err(ValidationError::MissingTx);
}
Expand Down Expand Up @@ -218,7 +250,7 @@ impl da::DaVerifier for CelestiaVerifier {
}
}

Ok(())
Ok(validity_condition)
}
}

Expand Down
8 changes: 8 additions & 0 deletions adapters/risc0/src/guest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ impl ZkvmGuest for Risc0Guest {
fn read_from_host<T: serde::de::DeserializeOwned>(&self) -> T {
env::read()
}

fn commit<T: serde::Serialize>(&self, item: &T) {
env::commit(item);
}
}

#[cfg(not(target_os = "zkvm"))]
impl ZkvmGuest for Risc0Guest {
fn read_from_host<T: serde::de::DeserializeOwned>(&self) -> T {
unimplemented!("This method should only be called in zkvm mode")
}

fn commit<T: serde::Serialize>(&self, _item: &T) {
unimplemented!("This method should only be called in zkvm mode")
}
}

impl Zkvm for Risc0Guest {
Expand Down
23 changes: 16 additions & 7 deletions examples/demo-prover/methods/guest/src/bin/rollup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ use jupiter::{BlobWithSender, CelestiaHeader};
use log::info;
use risc0_adapter::guest::Risc0Guest;
use risc0_zkvm::guest::env;
use sov_rollup_interface::crypto::NoOpHasher;
use sov_rollup_interface::da::{DaSpec, DaVerifier};
use sov_rollup_interface::services::stf_runner::StateTransitionRunner;
use sov_rollup_interface::stf::{StateTransitionFunction, ZkConfig};
use sov_rollup_interface::zk::traits::ZkvmGuest;
use sov_rollup_interface::zk::traits::{StateTransition, ValidityCondition, ZkvmGuest};

// The rollup stores its data in the namespace b"sov-test" on Celestia
const ROLLUP_NAMESPACE: NamespaceId = NamespaceId(ROLLUP_NAMESPACE_RAW);
Expand Down Expand Up @@ -46,17 +47,20 @@ pub fn main() {
let completeness_proof: <CelestiaSpec as DaSpec>::CompletenessProof = guest.read_from_host();

// Step 2: Verify tx list
verifier
.verify_relevant_tx_list(&header, &txs, inclusion_proof, completeness_proof)
let validity_condition = verifier
.verify_relevant_tx_list::<NoOpHasher>(&header, &txs, inclusion_proof, completeness_proof)
.expect("Transaction list must be correct");
env::write(&"Relevant txs verified\n");

state_transition(&guest, txs);
state_transition(&guest, txs, validity_condition);
}

fn state_transition(guest: &Risc0Guest, batches: Vec<BlobWithSender>) {
fn state_transition(
guest: &Risc0Guest,
batches: Vec<BlobWithSender>,
validity_condition: impl ValidityCondition,
) {
let prev_state_root_hash: [u8; 32] = guest.read_from_host();
env::commit_slice(&prev_state_root_hash[..]);
env::write(&"Prev root hash read\n");

let mut demo_runner = <ZkAppRunner<Risc0Guest> as StateTransitionRunner<
Expand All @@ -77,7 +81,12 @@ fn state_transition(guest: &Risc0Guest, batches: Vec<BlobWithSender>) {
}
let (state_root, _) = demo.end_slot();
env::write(&"Slot has ended\n");
env::commit(&state_root);
let output = StateTransition {
initial_state_root: prev_state_root_hash,
final_state_root: state_root.0,
validity_condition,
};
env::commit(&output);
env::write(&"new state root committed\n");
}

Expand Down
40 changes: 36 additions & 4 deletions examples/demo-rollup/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ use demo_stf::runtime::GenesisConfig;
use jsonrpsee::core::server::rpc_module::Methods;
use jupiter::da_service::CelestiaService;
use jupiter::types::NamespaceId;
use jupiter::verifier::CelestiaVerifier;
use jupiter::verifier::RollupParams;
use jupiter::verifier::{CelestiaVerifier, ChainValidityCondition};
use risc0_adapter::host::Risc0Verifier;
use sov_db::ledger_db::{LedgerDB, SlotCommit};
use sov_rollup_interface::crypto::NoOpHasher;
use sov_rollup_interface::da::DaVerifier;
use sov_rollup_interface::services::da::{DaService, SlotData};
use sov_rollup_interface::services::stf_runner::StateTransitionRunner;
use sov_rollup_interface::stf::StateTransitionFunction;
use sov_rollup_interface::traits::CanonicalHash;
use sov_rollup_interface::zk::traits::ValidityConditionChecker;
use sov_state::Storage;
use std::env;
use std::net::SocketAddr;
Expand Down Expand Up @@ -74,6 +77,22 @@ pub fn get_genesis_config() -> GenesisConfig<DefaultContext> {
)
}

pub struct CelestiaChainChecker {
current_block_hash: [u8; 32],
}

impl ValidityConditionChecker<ChainValidityCondition> for CelestiaChainChecker {
type Error = anyhow::Error;

fn check(&mut self, condition: &ChainValidityCondition) -> Result<(), anyhow::Error> {
anyhow::ensure!(
condition.block_hash == self.current_block_hash,
"Invalid block hash"
);
Ok(())
}
}

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let rollup_config_path = env::args()
Expand Down Expand Up @@ -188,9 +207,22 @@ async fn main() -> Result<(), anyhow::Error> {
let (inclusion_proof, completeness_proof) =
da_service.get_extraction_proof(&filtered_block, &blob_txs);

assert!(da_verifier
.verify_relevant_tx_list(header, &blob_txs, inclusion_proof, completeness_proof)
.is_ok());
let validity_condition = da_verifier
.verify_relevant_tx_list::<NoOpHasher>(
header,
&blob_txs,
inclusion_proof,
completeness_proof,
)
.expect("Failed to verify relevant tx list but prover is honest");

// For demonstration purposes, we also show how you would check the extra validity condition
// imposed by celestia (that the Celestia block processed be the next one from the canonical chain).
// In a real rollup, this check would only be made by light clients.
let mut checker = CelestiaChainChecker {
current_block_hash: *header.hash().inner(),
};
checker.check(&validity_condition)?;

// Store the resulting receipts in the ledger database
ledger_db.commit_slot(data_to_commit)?;
Expand Down
2 changes: 1 addition & 1 deletion module-system/sov-modules-api/src/default_context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::default_signature::{DefaultPublicKey, DefaultSignature};
use crate::{Address, AddressTrait, Context, PublicKey, Spec};
use sov_rollup_interface::crypto::SimpleHasher;

use jmt::SimpleHasher;
#[cfg(feature = "native")]
use serde::{Deserialize, Serialize};
use sov_state::DefaultStorageSpec;
Expand Down
2 changes: 1 addition & 1 deletion module-system/sov-modules-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ use borsh::{BorshDeserialize, BorshSerialize};
use core::fmt::{self, Debug, Display};
pub use dispatch::{DispatchCall, Genesis};
pub use error::Error;
pub use jmt::SimpleHasher as Hasher;
pub use prefix::Prefix;
pub use response::CallResponse;
use serde::{Deserialize, Serialize};
pub use sov_rollup_interface::crypto::SimpleHasher as Hasher;
pub use sov_rollup_interface::traits::AddressTrait;
use sov_state::{Storage, Witness, WorkingSet};
use thiserror::Error;
Expand Down
2 changes: 1 addition & 1 deletion module-system/sov-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub trait MerkleProofSpec {
/// The structure that accumulates the witness data
type Witness: Witness;
/// The hash function used to compute the merkle root
type Hasher: jmt::SimpleHasher;
type Hasher: sov_rollup_interface::crypto::SimpleHasher;
}

use sha2::Sha256;
Expand Down
6 changes: 4 additions & 2 deletions module-system/sov-state/src/prover_storage.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::marker::PhantomData;
use std::{fs, path::Path, sync::Arc};

use crate::config::Config;
Expand All @@ -8,12 +9,13 @@ use crate::{
tree_db::TreeReadLogger,
MerkleProofSpec, Storage,
};
use jmt::{storage::TreeWriter, JellyfishMerkleTree, KeyHash, PhantomHasher, SimpleHasher};
use jmt::{storage::TreeWriter, JellyfishMerkleTree, KeyHash};
use sov_db::state_db::StateDB;
use sov_rollup_interface::crypto::SimpleHasher;

pub struct ProverStorage<S: MerkleProofSpec> {
db: StateDB,
_phantom_hasher: PhantomHasher<S::Hasher>,
_phantom_hasher: PhantomData<S::Hasher>,
}

impl<S: MerkleProofSpec> Clone for ProverStorage<S> {
Expand Down
7 changes: 4 additions & 3 deletions module-system/sov-state/src/zk_storage.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::sync::Arc;
use std::{marker::PhantomData, sync::Arc};

use crate::witness::{TreeWitnessReader, Witness};
use jmt::{JellyfishMerkleTree, KeyHash, PhantomHasher, SimpleHasher, Version};
use jmt::{JellyfishMerkleTree, KeyHash, Version};
use sov_rollup_interface::crypto::SimpleHasher;

use crate::{
internal_cache::OrderedReadsAndWrites,
Expand All @@ -11,7 +12,7 @@ use crate::{

pub struct ZkStorage<S: MerkleProofSpec> {
prev_state_root: [u8; 32],
_phantom_hasher: PhantomHasher<S::Hasher>,
_phantom_hasher: PhantomData<S::Hasher>,
}

impl<S: MerkleProofSpec> Clone for ZkStorage<S> {
Expand Down
5 changes: 3 additions & 2 deletions rollup-interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ borsh = { workspace = true, features = ["rc"] }
serde = { workspace = true }
bytes = { workspace = true }
hex = { workspace = true, features = ["serde"] }
jmt = { workspace = true }

sha2 = { workspace = true }
sha2 = { workspace = true, optional = true }
tendermint = "0.32"

anyhow = { workspace = true }
Expand All @@ -38,4 +39,4 @@ serde_json = "1"
[features]
default = []
fuzzing = ["proptest"]
mocks = []
mocks = ["sha2"]
2 changes: 2 additions & 0 deletions rollup-interface/src/state_machine/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod simple_hasher;
pub use simple_hasher::{NoOpHasher, SimpleHasher};
15 changes: 15 additions & 0 deletions rollup-interface/src/state_machine/crypto/simple_hasher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pub use jmt::SimpleHasher;

/// A SimpleHasher implementation which always returns the digest [0;32]
pub struct NoOpHasher;
impl SimpleHasher for NoOpHasher {
fn new() -> Self {
Self
}

fn update(&mut self, _data: &[u8]) {}

fn finalize(self) -> [u8; 32] {
[0u8; 32]
}
}
10 changes: 8 additions & 2 deletions rollup-interface/src/state_machine/da.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::crypto::SimpleHasher;
use crate::traits::{AddressTrait, BlockHeaderTrait};
use crate::zk::traits::ValidityCondition;
use borsh::{BorshDeserialize, BorshSerialize};
use bytes::Buf;
use core::fmt::Debug;
Expand Down Expand Up @@ -46,16 +48,20 @@ pub trait DaVerifier {
/// TODO: Should we add `std::Error` bound so it can be `()?` ?
type Error: Debug;

/// Any conditions imposed by the DA layer which need to be checked outside of the SNARK
type ValidityCondition: ValidityCondition;

/// Create a new da verifier with the given chain parameters
fn new(params: <Self::Spec as DaSpec>::ChainParams) -> Self;

/// Verify a claimed set of transactions against a block header.
fn verify_relevant_tx_list(
fn verify_relevant_tx_list<H: SimpleHasher>(
&self,
block_header: &<Self::Spec as DaSpec>::BlockHeader,
txs: &[<Self::Spec as DaSpec>::BlobTransaction],
inclusion_proof: <Self::Spec as DaSpec>::InclusionMultiProof,
completeness_proof: <Self::Spec as DaSpec>::CompletenessProof,
) -> Result<(), Self::Error>;
) -> Result<Self::ValidityCondition, Self::Error>;
}

#[derive(Debug, Clone, Serialize, Deserialize, BorshDeserialize, BorshSerialize, PartialEq)]
Expand Down
Loading