From 7e787deefd079c3f2b3160785e58a6b4affc1340 Mon Sep 17 00:00:00 2001 From: Torben Poguntke Date: Tue, 2 Jun 2026 01:36:17 +0200 Subject: [PATCH] mithril_risc0: RISC0 host-converter support on top of 2617.0 Three patch groups, all needed for downstream RISC0 zkVM verifier consumers (e.g. mithril-dwarf) to construct, inspect, and serialize the new 2617.0 type hierarchy from their custom wire format. ## Build / dependency pins for RISC0 compatibility * mithril-common/Cargo.toml: pin ed25519-dalek to =2.1.1 (RISC0's curve25519-dalek precompile fork is tagged on the 2.1.x line; 2.2.0 is not yet supported by the precompile). * mithril-common/Cargo.toml: pin fixed to =1.29.0 (fixed 1.30+ requires rustc 1.93, ahead of what the RISC0 toolchain ships). * mithril-stm/Cargo.toml: pin blst to =0.3.15 (matches the blst version downstream consumers rely on for their BLS identity-point rejection behaviour pin; 0.3.16 tightens identity rejection which would break that pin's contract). ## API exposure for custom serializer / deserializer consumers mithril-stm/src/lib.rs: * Re-export MerkleBatchPath, MerkleTreeBatchCommitment, MerkleTreeConcatenationLeaf, MerkleTreeLeaf so downstream consumers don't have to reach into private modules. * Re-export ConcatenationProof and SingleSignatureForConcatenation from the proof_system layer (both were previously pub(crate)). * Gate `pub use hash::poseidon::MidnightPoseidonDigest` on `all(feature = "benchmark-internals", feature = "future_snark")`; the previous gate on `benchmark-internals` alone failed to compile when `future_snark` was off because `mod hash` is gated on `future_snark`. mithril-stm/src/membership_commitment/merkle_tree/commitment.rs: * Promote `MerkleTreeBatchCommitment::new` from `pub(crate)` to `pub`. * Add `pub fn nr_leaves(&self) -> usize` getter. mithril-stm/src/membership_commitment/merkle_tree/path.rs: * Add `pub fn values(&self) -> &[Vec]` and `pub fn indices(&self) -> &[usize]` getters on `MerkleBatchPath`. Needed because 2617.0 switched `to_bytes()` to CBOR with a version prefix, but downstream consumers maintain the legacy raw byte layout in their wire format and need direct access to the components to emit it. mithril-stm/src/proof_system/concatenation/aggregate_key.rs: * Add `pub fn new(mt_commitment, total_stake)` constructor and `pub fn get_mt_commitment(&self) -> &MerkleTreeBatchCommitment<...>` borrowing getter on `AggregateVerificationKeyForConcatenation`. mithril-stm/src/proof_system/concatenation/proof.rs: * Add `pub fn new(signatures, batch_proof)` constructor and `pub fn signatures(&self) -> &[SingleSignatureWithRegisteredParty]` borrowing getter on `ConcatenationProof`. mithril-stm/src/proof_system/concatenation/single_signature.rs: * Promote `SingleSignatureForConcatenation` from `pub(crate)` to `pub`. * Promote its `new(sigma, indexes)` constructor from `pub(crate)` to `pub` and add `sigma()` / `indexes()` borrowing getters. mithril-stm/src/proof_system/concatenation/mod.rs: mithril-stm/src/proof_system/mod.rs: * Re-export `SingleSignatureForConcatenation` publicly at both module layers (was `pub(crate)`). mithril-stm/src/protocol/single_signature/signature.rs: * Add `pub fn new(concatenation_signature, signer_index, ...)` constructor and `pub fn concatenation_signature(&self) -> &...` borrowing getter on `SingleSignature`. ## Defensive bounds check on legacy AVK decoder mithril-stm/src/proof_system/concatenation/aggregate_key.rs: * `AggregateVerificationKeyForConcatenation::from_bytes_legacy` previously sliced `&bytes[size - 8..]` without bounds-checking, so inputs shorter than 8 bytes caused an out-of-range slice panic instead of returning the proper SerializationError. Now bounds- checked via `checked_sub` so malformed input returns `MerkleTreeError::SerializationError` and the caller can handle it. Surfaces when upstream's `verify_aggregate_verification_key_chaining` calls `try_from(&str)` on a NextAvk string that fails to decode cleanly (e.g. during round-trip-canonicalisation testing). --- Cargo.lock | 14 ++++----- mithril-common/Cargo.toml | 8 +++-- mithril-stm/Cargo.toml | 4 ++- mithril-stm/src/lib.rs | 11 +++++-- .../merkle_tree/commitment.rs | 9 +++++- .../membership_commitment/merkle_tree/path.rs | 12 +++++++ .../concatenation/aggregate_key.rs | 31 +++++++++++++++++-- .../src/proof_system/concatenation/mod.rs | 2 +- .../src/proof_system/concatenation/proof.rs | 17 ++++++++++ .../concatenation/single_signature.rs | 16 ++++++++-- mithril-stm/src/proof_system/mod.rs | 3 +- .../protocol/single_signature/signature.rs | 20 ++++++++++++ 12 files changed, 126 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b3042389e1..30b26980c93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,9 +689,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -1571,7 +1571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -1715,9 +1715,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", @@ -1903,9 +1903,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixed" -version = "1.31.0" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af2cbf772fa6d1c11358f92ef554cb6b386201210bcf0e91fb7fba8a907fb40" +checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" dependencies = [ "az", "bytemuck", diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 9b033cd5812..f0255c09013 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -42,8 +42,12 @@ chrono = { workspace = true } ciborium = "0.2.2" ckb-merkle-mountain-range = "0.6.1" digest = { workspace = true } -ed25519-dalek = { version = "2.2.0", features = ["rand_core", "serde"] } -fixed = "1.31.0" +# Exact-pinned to 2.1.1 for compatibility with the RISC0 `curve25519-dalek` +# precompile fork (tagged on the 2.1.x line). +ed25519-dalek = { version = "=2.1.1", features = ["rand_core", "serde"] } +# Exact-pinned to 1.29.0 to stay buildable on the rustc the RISC0 +# toolchain ships (fixed 1.30+ requires rustc 1.93). +fixed = "=1.29.0" hex = { workspace = true } kes-summed-ed25519 = { version = "0.2.1", features = ["serde_enabled", "sk_clone_enabled"] } mithril-stm = { path = "../mithril-stm", version = ">=0.9", default-features = false } diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index fca72cbf8f7..8f2859fe8f8 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -38,7 +38,9 @@ rug-backend = ["rug/default"] anyhow = { workspace = true } blake2 = "0.10.6" # Enforce blst portable feature for runtime detection of Intel ADX instruction set. -blst = { version = "0.3.16", features = ["portable"] } +# Exact-pinned to 0.3.15 to match the dwarf harness divergence-1 pin +# (BLS identity-rejection layer). Bump in lockstep with that pin if upgraded. +blst = { version = "=0.3.15", features = ["portable"] } ciborium = "0.2.2" digest = { workspace = true } ff = { version = "0.13.1", optional = true } diff --git a/mithril-stm/src/lib.rs b/mithril-stm/src/lib.rs index 8f57a89adc1..fb2009c9a54 100644 --- a/mithril-stm/src/lib.rs +++ b/mithril-stm/src/lib.rs @@ -123,7 +123,9 @@ mod proof_system; mod protocol; mod signature_scheme; -pub use proof_system::AggregateVerificationKeyForConcatenation; +pub use proof_system::{ + AggregateVerificationKeyForConcatenation, ConcatenationProof, SingleSignatureForConcatenation, +}; pub use protocol::{ AggregateSignature, AggregateSignatureError, AggregateSignatureType, AggregateVerificationKey, AggregationError, Clerk, ClosedKeyRegistration, ClosedRegistrationEntry, Initializer, @@ -134,6 +136,11 @@ pub use protocol::{ }; pub use signature_scheme::BlsSignatureError; +// Re-export Merkle types for custom serializer / deserializer consumers (e.g. risc0). +pub use membership_commitment::{ + MerkleBatchPath, MerkleTreeBatchCommitment, MerkleTreeConcatenationLeaf, MerkleTreeLeaf, +}; + use blake2::{Blake2b, digest::consts::U32}; use digest::{Digest, FixedOutput}; use std::fmt::Debug; @@ -152,7 +159,7 @@ pub use signature_scheme::{ #[cfg(all(feature = "future_snark", not(feature = "benchmark-internals")))] use hash::poseidon::MidnightPoseidonDigest; -#[cfg(feature = "benchmark-internals")] +#[cfg(all(feature = "benchmark-internals", feature = "future_snark"))] pub use hash::poseidon::MidnightPoseidonDigest; #[cfg(feature = "future_snark")] diff --git a/mithril-stm/src/membership_commitment/merkle_tree/commitment.rs b/mithril-stm/src/membership_commitment/merkle_tree/commitment.rs index d24b9ff0a74..a29eb887580 100644 --- a/mithril-stm/src/membership_commitment/merkle_tree/commitment.rs +++ b/mithril-stm/src/membership_commitment/merkle_tree/commitment.rs @@ -118,7 +118,9 @@ pub struct MerkleTreeBatchCommitment { } impl MerkleTreeBatchCommitment { - pub(crate) fn new(root: Vec, nr_leaves: usize) -> Self { + // Made public for AVK reconstruction from byte form in custom serializer + // consumers (e.g. risc0). + pub fn new(root: Vec, nr_leaves: usize) -> Self { Self { root, nr_leaves, @@ -127,6 +129,11 @@ impl MerkleTreeBatchCommitment } } + /// Number of leaves in the committed Merkle tree. + pub fn nr_leaves(&self) -> usize { + self.nr_leaves + } + #[cfg(feature = "future_snark")] // TODO: remove this allow dead_code directive when function is called or future_snark is activated #[allow(dead_code)] diff --git a/mithril-stm/src/membership_commitment/merkle_tree/path.rs b/mithril-stm/src/membership_commitment/merkle_tree/path.rs index 1cce05985f1..2273cc2984f 100644 --- a/mithril-stm/src/membership_commitment/merkle_tree/path.rs +++ b/mithril-stm/src/membership_commitment/merkle_tree/path.rs @@ -103,6 +103,18 @@ impl MerkleBatchPath { codec::to_cbor_bytes(self) } + /// Borrow the path's value list. Exposed for custom serializer / deserializer + /// consumers (e.g. risc0) that need the legacy raw byte layout. + pub fn values(&self) -> &[Vec] { + &self.values + } + + /// Borrow the path's index list. Exposed for custom serializer / deserializer + /// consumers (e.g. risc0) that need the legacy raw byte layout. + pub fn indices(&self) -> &[usize] { + &self.indices + } + /// Try to convert a byte string into a `BatchPath`. pub fn from_bytes(bytes: &[u8]) -> StmResult { codec::from_versioned_bytes(bytes, Self::from_bytes_legacy) diff --git a/mithril-stm/src/proof_system/concatenation/aggregate_key.rs b/mithril-stm/src/proof_system/concatenation/aggregate_key.rs index 98d87e6970c..d36be21f236 100644 --- a/mithril-stm/src/proof_system/concatenation/aggregate_key.rs +++ b/mithril-stm/src/proof_system/concatenation/aggregate_key.rs @@ -20,6 +20,18 @@ pub struct AggregateVerificationKeyForConcatenation { } impl AggregateVerificationKeyForConcatenation { + /// Construct from already-built components. Exposed for custom + /// serializer / deserializer consumers (e.g. risc0). + pub fn new( + mt_commitment: MerkleTreeBatchCommitment, + total_stake: Stake, + ) -> Self { + Self { + mt_commitment, + total_stake, + } + } + /// Get the Merkle tree batch commitment. pub(crate) fn get_merkle_tree_batch_commitment( &self, @@ -27,6 +39,14 @@ impl AggregateVerificationKeyForConcatenation { self.mt_commitment.clone() } + /// Borrow the Merkle tree batch commitment. Exposed for custom + /// serializer consumers that need byte-level access to root + nr_leaves. + pub fn get_mt_commitment( + &self, + ) -> &MerkleTreeBatchCommitment { + &self.mt_commitment + } + /// Get the total stake. pub fn get_total_stake(&self) -> Stake { self.total_stake @@ -48,11 +68,16 @@ impl AggregateVerificationKeyForConcatenation { let mut u64_bytes = [0u8; 8]; let size = bytes.len(); + // Guard against short inputs: a legacy AVK ends with an 8-byte + // stake suffix. Without this check, `&bytes[size - 8..]` panics + // with an out-of-range slice index on inputs shorter than 8 + // bytes instead of returning a proper serialization error. + let mt_commitment_slice = bytes + .get(..size.checked_sub(8).ok_or(MerkleTreeError::SerializationError)?) + .ok_or(MerkleTreeError::SerializationError)?; u64_bytes.copy_from_slice(&bytes[size - 8..]); let stake = u64::from_be_bytes(u64_bytes); - let mt_commitment = MerkleTreeBatchCommitment::from_bytes( - bytes.get(..size - 8).ok_or(MerkleTreeError::SerializationError)?, - )?; + let mt_commitment = MerkleTreeBatchCommitment::from_bytes(mt_commitment_slice)?; Ok(Self { mt_commitment, total_stake: stake, diff --git a/mithril-stm/src/proof_system/concatenation/mod.rs b/mithril-stm/src/proof_system/concatenation/mod.rs index fc0ecfb5b62..435dee0c980 100644 --- a/mithril-stm/src/proof_system/concatenation/mod.rs +++ b/mithril-stm/src/proof_system/concatenation/mod.rs @@ -10,4 +10,4 @@ pub use clerk::ConcatenationClerk; pub(super) use eligibility::is_lottery_won; pub use proof::ConcatenationProof; pub(crate) use signer::ConcatenationProofSigner; -pub(crate) use single_signature::SingleSignatureForConcatenation; +pub use single_signature::SingleSignatureForConcatenation; diff --git a/mithril-stm/src/proof_system/concatenation/proof.rs b/mithril-stm/src/proof_system/concatenation/proof.rs index f0050278e55..028becac4be 100644 --- a/mithril-stm/src/proof_system/concatenation/proof.rs +++ b/mithril-stm/src/proof_system/concatenation/proof.rs @@ -38,6 +38,23 @@ pub struct ConcatenationProof { } impl ConcatenationProof { + /// Construct a `ConcatenationProof` from already-aggregated components. + /// Exposed for custom serializer / deserializer consumers (e.g. risc0). + pub fn new( + signatures: Vec, + batch_proof: MerkleBatchPath, + ) -> Self { + Self { + signatures, + batch_proof, + } + } + + /// Borrow the underlying signatures. Exposed for custom serializer consumers. + pub fn signatures(&self) -> &[SingleSignatureWithRegisteredParty] { + &self.signatures + } + /// Aggregate a set of signatures for their corresponding indices. /// /// This function first deduplicates the repeated signatures, and if there are enough signatures, it collects the merkle tree indexes of unique signatures. diff --git a/mithril-stm/src/proof_system/concatenation/single_signature.rs b/mithril-stm/src/proof_system/concatenation/single_signature.rs index 065811cc24e..64ff4d7c9ef 100644 --- a/mithril-stm/src/proof_system/concatenation/single_signature.rs +++ b/mithril-stm/src/proof_system/concatenation/single_signature.rs @@ -13,7 +13,7 @@ use super::is_lottery_won; /// Single signature for the concatenation proof system. #[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct SingleSignatureForConcatenation { +pub struct SingleSignatureForConcatenation { /// The underlying BLS signature sigma: BlsSignature, /// The index(es) for which the signature is valid @@ -22,11 +22,21 @@ pub(crate) struct SingleSignatureForConcatenation { impl SingleSignatureForConcatenation { /// Create and return a new instance of `SingleSignatureForConcatenation` for given `sigma` and - /// `indexes`. - pub(crate) fn new(sigma: BlsSignature, indexes: Vec) -> Self { + /// `indexes`. Exposed for custom serializer / deserializer consumers (e.g. risc0). + pub fn new(sigma: BlsSignature, indexes: Vec) -> Self { Self { sigma, indexes } } + /// Borrow the underlying BLS signature. Exposed for custom serializer consumers. + pub fn sigma(&self) -> &BlsSignature { + &self.sigma + } + + /// Borrow the lottery indices the signature is valid for. Exposed for custom serializer consumers. + pub fn indexes(&self) -> &[LotteryIndex] { + &self.indexes + } + /// Verify a `SingleSignatureForConcatenation` by validating the underlying BLS signature and checking /// that the lottery was won for all indexes. pub(crate) fn verify( diff --git a/mithril-stm/src/proof_system/mod.rs b/mithril-stm/src/proof_system/mod.rs index 8cebdbb1fa9..cf1f72f3d72 100644 --- a/mithril-stm/src/proof_system/mod.rs +++ b/mithril-stm/src/proof_system/mod.rs @@ -22,8 +22,9 @@ mod halo2_snark; pub use concatenation::{ AggregateVerificationKeyForConcatenation, ConcatenationClerk, ConcatenationProof, + SingleSignatureForConcatenation, }; -pub(crate) use concatenation::{ConcatenationProofSigner, SingleSignatureForConcatenation}; +pub(crate) use concatenation::ConcatenationProofSigner; #[cfg(feature = "future_snark")] pub use halo2_snark::AggregateVerificationKeyForSnark; diff --git a/mithril-stm/src/protocol/single_signature/signature.rs b/mithril-stm/src/protocol/single_signature/signature.rs index 88a97f0d7fa..fc80dd609ba 100644 --- a/mithril-stm/src/protocol/single_signature/signature.rs +++ b/mithril-stm/src/protocol/single_signature/signature.rs @@ -30,6 +30,26 @@ pub struct SingleSignature { } impl SingleSignature { + /// Construct a `SingleSignature` from already-built components. + /// Exposed for custom serializer / deserializer consumers (e.g. risc0). + pub fn new( + concatenation_signature: SingleSignatureForConcatenation, + signer_index: SignerIndex, + #[cfg(feature = "future_snark")] snark_signature: Option, + ) -> Self { + Self { + concatenation_signature, + signer_index, + #[cfg(feature = "future_snark")] + snark_signature, + } + } + + /// Borrow the concatenation-proof-system signature. Exposed for custom serializer consumers. + pub fn concatenation_signature(&self) -> &SingleSignatureForConcatenation { + &self.concatenation_signature + } + /// Verify a `SingleSignature` by validating the underlying single signature for proof system. pub fn verify( &self,