Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion mithril-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fixed = "1.31.0"
hex = { workspace = true }
kes-summed-ed25519 = { version = "0.2.1", features = ["serde_enabled", "sk_clone_enabled"] }
mithril-merkle-tree = { path = "../internal/mithril-merkle-tree", version = "0.1.4" }
mithril-stm = { path = "../mithril-stm", version = "0.12.19", default-features = false }
mithril-stm = { path = "../mithril-stm", version = "0.12.20", default-features = false }
nom = "8.0.0"
rand_chacha = { workspace = true }
rand_core = { workspace = true }
Expand Down
18 changes: 18 additions & 0 deletions mithril-stm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.12.20 (09-21-2026)

### Changed

- Moved the IVC circuit onto the Midnight standard library's `Relation` trait, replacing the hand-written configuration. The statement and the degree are unchanged, but the standard library registers gates in a different order, so the recursive verifying key moves: a breaking circuit key change requiring a re-genesis, with every derived asset regenerated.
- Encoded the recursive circuit keys in the certificate keys' format, through one shared codec. The inner key is byte-identical, so this adds a 23-byte envelope without altering circuit identity.
- Bound the recursive key into the recursive circuit's cache identity, non-production entries having been fingerprinted from the certificate key alone.
- Borrowed the witness during synthesis instead of copying it once per assigned part.

### Fixed

- Rejected a key of the wrong circuit, degree or shape before it reaches the dependency's readers, on both verifying keys and on the recursive proving key. One shared key format left each circuit's key decodable in the other's position, where a chosen architecture, degree or fixed commitment count reaches a panic.

### Added

- Documented the constants that asset regeneration must update, with the assertion orientation of each.
- Decoded the cached key pairs through their typed decoders in the prover setup test, the recursive proving key having had no coverage on a valid key.

## 0.12.19 (09-15-2026)

### Added
Expand Down
2 changes: 1 addition & 1 deletion mithril-stm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-stm"
version = "0.12.19"
version = "0.12.20"
edition = { workspace = true }
authors = { workspace = true }
homepage = { workspace = true }
Expand Down
40 changes: 24 additions & 16 deletions mithril-stm/src/circuits/halo2/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ use crate::signature_scheme::{
};
use crate::{LotteryIndex, Parameters, StmResult};

/// Chips the certificate circuit enables.
///
/// Single source: the relation declares these to the standard library, and the key decoder checks
/// an encoded key against the same value, so the two cannot drift.
pub(crate) fn certificate_circuit_architecture() -> ZkStdLibArch {
ZkStdLibArch {
jubjub: true,
poseidon: true,
sha2_256: false,
sha2_512: false,
keccak_256: false,
sha3_256: false,
secp256k1: false,
bls12_381: false,
base64: false,
nr_pow2range_cols: 2,
automaton: false,
blake2b: false,
curve25519: false,
p256: false,
}
}

/// Halo2 relation implementing the non-recursive STM verification circuit.
///
/// Carries only the parameters that fix the constraint system; the instance and witness are
Expand Down Expand Up @@ -296,22 +319,7 @@ impl Relation for CertificateCircuit {
}

fn used_chips(&self) -> ZkStdLibArch {
ZkStdLibArch {
jubjub: true,
poseidon: true,
sha2_256: false,
sha2_512: false,
keccak_256: false,
sha3_256: false,
secp256k1: false,
bls12_381: false,
base64: false,
nr_pow2range_cols: 2,
automaton: false,
blake2b: false,
curve25519: false,
p256: false,
}
certificate_circuit_architecture()
}

fn write_relation<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
Expand Down
20 changes: 20 additions & 0 deletions mithril-stm/src/circuits/halo2/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,26 @@ pub enum CertificateCircuitError {
/// A proving/verification backend error surfaced at the relation boundary.
#[error("Backend error: {0}")]
Backend(String),

/// An encoded verification key declares an architecture that is not the certificate circuit's.
#[error("The encoded key does not declare the certificate circuit architecture")]
VerificationKeyArchitectureMismatch,

/// An encoded verification key declares a degree no certificate circuit could have been
/// generated with, or declares two degrees that disagree.
#[error("Certificate verification key degree mismatch: expected {expected}, got {actual}")]
VerificationKeyDegreeMismatch { expected: u32, actual: u32 },

/// A standalone encoded verification key is followed by bytes that are not part of it.
#[error("The encoded certificate verification key has {trailing} trailing byte(s)")]
VerificationKeyEncodingHasTrailingBytes { trailing: usize },

/// An encoded verification key declares fewer fixed commitments than its constraint system has
/// fixed columns, which the verifier indexes by column.
#[error(
"Certificate verification key fixed commitment count mismatch: expected {expected}, got {actual}"
)]
VerificationKeyCommitmentCountMismatch { expected: usize, actual: usize },
}

impl From<PlonkError> for CertificateCircuitError {
Expand Down
38 changes: 9 additions & 29 deletions mithril-stm/src/circuits/halo2/key_serialization.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,15 @@
//! [`TryToBytes`] / [`TryFromBytes`] impls for the certificate circuit's keys — the self-describing
//! Midnight `MidnightVK` / `MidnightPK` wrappers. Production keys use [`SerdeFormat::RawBytes`].
//! [`TryToBytes`] / [`TryFromBytes`] impls for the certificate circuit's proving key. Its verifying
//! key is a `MidnightVK`, whose shared encoding lives in [`crate::circuits::key_serialization`].

use anyhow::Context;
use midnight_proofs::utils::SerdeFormat;
use midnight_zk_stdlib::{MidnightPK, MidnightVK};
use midnight_zk_stdlib::MidnightPK;

use crate::StmResult;
use crate::circuits::key_serialization::KEY_SERDE_FORMAT;
use crate::codec::{TryFromBytes, TryToBytes};

use super::circuit::CertificateCircuit;

/// Serde format used for the on-disk / in-cache production keys.
const KEY_SERDE_FORMAT: SerdeFormat = SerdeFormat::RawBytes;

// Certificate circuit verifying key. `MidnightVK` is self-describing, so reading needs only the
// serde format (no circuit type), unlike the recursive raw PLONK keys.
impl TryToBytes for MidnightVK {
fn to_bytes_vec(&self) -> StmResult<Vec<u8>> {
let mut bytes = Vec::new();
self.write(&mut bytes, KEY_SERDE_FORMAT)
.with_context(|| "Failed to serialize the certificate verifying key")?;
Ok(bytes)
}
}

impl TryFromBytes for MidnightVK {
fn try_from_bytes(bytes: &[u8]) -> StmResult<Self> {
let mut reader = bytes;
MidnightVK::read(&mut reader, KEY_SERDE_FORMAT)
.with_context(|| "Failed to deserialize the certificate verifying key")
}
}

// Certificate circuit proving key.
impl TryToBytes for MidnightPK<CertificateCircuit> {
fn to_bytes_vec(&self) -> StmResult<Vec<u8>> {
Expand Down Expand Up @@ -60,12 +38,14 @@ mod tests {
use super::*;
use crate::Parameters;
use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION;
use crate::circuits::halo2::keys::NonRecursiveCircuitVerifyingKey;

#[test]
fn production_verifying_key_serializes_to_the_embedded_bytes() {
let verifying_key =
MidnightVK::try_from_bytes(NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION)
.expect("production verifying key bytes should deserialize");
let verifying_key = NonRecursiveCircuitVerifyingKey::try_from_bytes(
NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION,
)
.expect("production verifying key bytes should deserialize");
assert_eq!(
verifying_key.to_bytes_vec().expect("serialize should succeed"),
NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION,
Expand Down
Loading
Loading