Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ As a minor extension, we have adopted a slightly different versioning convention

## Mithril Distribution [XXXX] - UNRELEASED

- **UNSTABLE**:
- Support for bytes encoding of the SNARK aggregate signatures in the certificates.
- Reduced the encoded size of the SNARK proofs by serializing their bytes as CBOR byte strings.

| Crate | Version |
| ------------------- | ------- |
| mithril-aggregator | `-` |
Expand Down
7 changes: 4 additions & 3 deletions Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ anyhow = { workspace = true }
async-trait = { workspace = true }
digest = { workspace = true }
hex = { workspace = true }
mithril-common = { path = "../../../mithril-common", version = "0.7.15" }
mithril-common = { path = "../../../mithril-common", version = "0.7.16" }
serde = { workspace = true }
serde_json = { workspace = true }
sha2 = "0.10.9"
Expand Down
2 changes: 1 addition & 1 deletion internal/mithril-aggregator-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ include = ["**/*.rs", "Cargo.toml", "README.md"]
[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
mithril-common = { path = "../../mithril-common", version = "0.7.15" }
mithril-common = { path = "../../mithril-common", version = "0.7.16" }
reqwest = { workspace = true }
semver = { workspace = true }
serde = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion internal/mithril-aggregator-discovery/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"]
anyhow = { workspace = true }
async-trait = { workspace = true }
mithril-aggregator-client = { path = "../mithril-aggregator-client", version = "0.2.4" }
mithril-common = { path = "../../mithril-common", version = "0.7.15" }
mithril-common = { path = "../../mithril-common", version = "0.7.16" }
rand = { version = "0.10.2" }
reqwest = { workspace = true }
serde = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion mithril-aggregator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-aggregator"
version = "0.9.19"
version = "0.9.20"
description = "A Mithril Aggregator server"
authors = { workspace = true }
edition = { workspace = true }
Expand Down
49 changes: 42 additions & 7 deletions mithril-aggregator/src/database/record/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,44 +215,48 @@ impl TryFrom<Certificate> for CertificateRecord {
fn try_from(other: Certificate) -> Result<Self, Self::Error> {
let signed_entity_type = other.signed_entity_type();
let (signature, parent_certificate_id) = match other.signature {
CertificateSignature::GenesisSignature(signature) => (signature.to_bytes_hex()?, None),
CertificateSignature::GenesisSignature(signature) => {
(String::try_from(&signature)?, None)
}
#[cfg(feature = "future_snark")]
CertificateSignature::GenesisDualSignature(ed_signature, schnorr_signature) => {
let payload = PersistedDualGenesisSignature {
ed25519: ed_signature.to_bytes_hex()?,
ed25519: String::try_from(&ed_signature)?,
schnorr: schnorr_signature_to_hex(&schnorr_signature),
};
(serde_json::to_string(&payload)?, None)
}
CertificateSignature::MultiSignature(_, signature) => {
(signature.to_json_hex()?, Some(other.previous_hash))
(String::try_from(&signature)?, Some(other.previous_hash))
}
};

#[cfg(feature = "future_snark")]
let aggregate_verification_key_snark = other
.aggregate_verification_key_snark
.as_ref()
.map(|avk| avk.to_bytes_hex())
.map(String::try_from)
.transpose()?;
#[cfg(not(feature = "future_snark"))]
let aggregate_verification_key_snark: Option<HexEncodedKey> = None;

let ancillary_prover_data = other
.ancillary_prover_data
.map(|data| data.to_bytes_hex())
.as_ref()
.map(String::try_from)
.transpose()?;
let ancillary_verifier_data = other
.ancillary_verifier_data
.map(|data| data.to_bytes_hex())
.as_ref()
.map(String::try_from)
.transpose()?;

let certificate_record = CertificateRecord {
certificate_id: other.hash,
parent_certificate_id,
message: other.signed_message,
signature,
aggregate_verification_key: other.aggregate_verification_key.to_json_hex()?,
aggregate_verification_key: String::try_from(&other.aggregate_verification_key)?,
aggregate_verification_key_snark,
ancillary_prover_data,
ancillary_verifier_data,
Expand Down Expand Up @@ -597,6 +601,37 @@ mod tests {
assert_eq!(expected_hash, &certificate.hash);
}

#[cfg(feature = "future_snark")]
mod snark_multi_signature {
use super::*;

#[test]
fn snark_multi_signature_is_stored_as_bytes_hex() {
let chain = setup_certificate_chain(5, 2);
let mut certificate = chain
.certificates_chained
.iter()
.find(|c| matches!(c.signature, CertificateSignature::MultiSignature(..)))
.expect("chain should contain a multi-signature certificate")
.clone();
let signed_entity_type = match &certificate.signature {
CertificateSignature::MultiSignature(signed_entity_type, _) => {
signed_entity_type.clone()
}
_ => unreachable!(),
};
certificate.signature = CertificateSignature::MultiSignature(
signed_entity_type,
fake_data::snark_aggregate_signature(),
);
let expected_bytes_hex = fake_data::snark_aggregate_signature().to_bytes_hex().unwrap();

let record: CertificateRecord = certificate.try_into().unwrap();

assert_eq!(expected_bytes_hex, record.signature);
}
}

#[cfg(feature = "future_snark")]
mod snark_aggregate_verification_key {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion mithril-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ flate2 = { version = "1.1.9", optional = true }
flume = { version = "0.12.0", optional = true }
futures = "0.3.32"
mithril-aggregator-client = { path = "../internal/mithril-aggregator-client", version = "0.2.4" }
mithril-common = { path = "../mithril-common", version = "0.7.15", default-features = false }
mithril-common = { path = "../mithril-common", version = "0.7.16", default-features = false }
reqwest = { workspace = true, default-features = false, features = ["charset", "http2", "stream", "system-proxy"] }
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions mithril-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-common"
version = "0.7.15"
version = "0.7.16"
description = "Common types, interfaces, and utilities for Mithril nodes."
authors = { workspace = true }
edition = { workspace = true }
Expand Down 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.1", default-features = false }
mithril-stm = { path = "../mithril-stm", version = "0.12.2", default-features = false }
nom = "8.0.0"
rand_chacha = { workspace = true }
rand_core = { workspace = true }
Expand Down
45 changes: 40 additions & 5 deletions mithril-common/src/crypto_helper/types/wrappers.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use kes_summed_ed25519::kes::Sum6KesSig;
use mithril_stm::{
AggregateSignature, AggregateVerificationKey, AggregateVerificationKeyForConcatenation,
AncillaryProverData, AncillaryVerifierData, SingleSignature,
VerificationKeyProofOfPossessionForConcatenation,
AggregateSignature, AggregateSignatureType, AggregateVerificationKey,
AggregateVerificationKeyForConcatenation, AncillaryProverData, AncillaryVerifierData,
SingleSignature, VerificationKeyProofOfPossessionForConcatenation,
};
#[cfg(feature = "future_snark")]
use mithril_stm::{AggregateVerificationKeyForSnark, VerificationKeyForSnark};

use crate::crypto_helper::{MKMapProof, MKProof, OpCert, ProtocolKey, ProtocolMembershipDigest};
use crate::StdResult;
use crate::crypto_helper::{
MKMapProof, MKProof, OpCert, ProtocolKey, ProtocolKeyCodec, ProtocolMembershipDigest,
};
use crate::entities::BlockRange;

/// Wrapper of [MithrilStm:VerificationKeyProofOfPossessionForConcatenation](type@VerificationKeyProofOfPossessionForConcatenation) to add serialization
Expand Down Expand Up @@ -60,14 +63,46 @@ pub type ProtocolAncillaryProverData = ProtocolKey<AncillaryProverData>;
pub type ProtocolAncillaryVerifierData = ProtocolKey<AncillaryVerifierData>;

impl_codec_and_type_conversions_for_protocol_key!(
json_hex_codec => AggregateSignature<ProtocolMembershipDigest>, ed25519_dalek::VerifyingKey, ed25519_dalek::SigningKey, AggregateVerificationKeyForConcatenation<ProtocolMembershipDigest>,
json_hex_codec => ed25519_dalek::VerifyingKey, ed25519_dalek::SigningKey, AggregateVerificationKeyForConcatenation<ProtocolMembershipDigest>,
MKProof, VerificationKeyProofOfPossessionForConcatenation, Sum6KesSig, OpCert, SingleSignature
);

impl_codec_and_type_conversions_for_protocol_key!(
bytes_hex_codec => ed25519_dalek::Signature, AncillaryProverData, AncillaryVerifierData
);

impl_codec_and_type_conversions_for_protocol_key!(
no_default_codec => AggregateSignature<ProtocolMembershipDigest>
);

impl ProtocolKeyCodec<AggregateSignature<ProtocolMembershipDigest>>
for AggregateSignature<ProtocolMembershipDigest>
{
fn decode_key(
encoded: &str,
) -> StdResult<ProtocolKey<AggregateSignature<ProtocolMembershipDigest>>> {
match ProtocolKey::from_json_hex(encoded) {
Ok(res) => Ok(res),
Err(_) => ProtocolKey::from_bytes_hex(encoded),
}
}

fn encode_key(key: &AggregateSignature<ProtocolMembershipDigest>) -> StdResult<String> {
// Temporary workaround: encode by aggregate signature type rather than a single codec. The
// bytes encoding of an aggregate signature is only decodable by clients from distribution
// 2617.0 onwards, so concatenation multi-signatures must stay JSON-hex until the older
// distributions (up to 2603.1) are retired. Once they are, register `AggregateSignature`
// under `bytes_hex_codec` and drop this custom codec.
match AggregateSignatureType::from(key) {
AggregateSignatureType::Concatenation => ProtocolKey::key_to_json_hex(key),
#[cfg(feature = "future_snark")]
AggregateSignatureType::Snark | AggregateSignatureType::IvcSnark => {
ProtocolKey::key_to_bytes_hex(key)
}
}
}
}

#[cfg(feature = "future_snark")]
impl_codec_and_type_conversions_for_protocol_key!(
json_hex_codec => VerificationKeyForSnark
Expand Down
57 changes: 48 additions & 9 deletions mithril-common/src/messages/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ impl TryFrom<Certificate> for CertificateMessage {
let (multi_signature, genesis_signature) = match certificate.signature {
CertificateSignature::GenesisSignature(signature) => (
String::new(),
signature.to_bytes_hex().with_context(|| {
String::try_from(&signature).with_context(|| {
"Can not convert certificate to message: can not encode the genesis signature"
})?,
),
Expand All @@ -279,13 +279,13 @@ impl TryFrom<Certificate> for CertificateMessage {
genesis_schnorr_signature = hex::encode(schnorr_signature.to_bytes());
(
String::new(),
ed_signature.to_bytes_hex().with_context(|| {
String::try_from(&ed_signature).with_context(|| {
"Can not convert certificate to message: can not encode the genesis signature"
})?,
)
}
CertificateSignature::MultiSignature(_, signature) => (
signature.to_json_hex().with_context(|| {
String::try_from(&signature).with_context(|| {
"Can not convert certificate to message: can not encode the multi-signature"
})?,
String::new(),
Expand All @@ -300,30 +300,28 @@ impl TryFrom<Certificate> for CertificateMessage {
metadata,
protocol_message: certificate.protocol_message,
signed_message: certificate.signed_message,
aggregate_verification_key: certificate
.aggregate_verification_key
.to_json_hex()
aggregate_verification_key: String::try_from(&certificate.aggregate_verification_key)
.with_context(|| {
"Can not convert certificate to message: can not encode aggregate verification key for Concatenation"
})?,
#[cfg(feature = "future_snark")]
aggregate_verification_key_snark: certificate
.aggregate_verification_key_snark
.map(|avk| avk.to_bytes_hex())
.map(String::try_from)
.transpose()
.with_context(|| {
"Can not convert certificate to message: can not encode aggregate verification key for SNARK"
})?,
ancillary_prover_data: certificate
.ancillary_prover_data
.map(|data| data.to_bytes_hex())
.map(String::try_from)
.transpose()
.with_context(|| {
"Can not convert certificate to message: can not encode the ancillary prover data"
})?,
ancillary_verifier_data: certificate
.ancillary_verifier_data
.map(|data| data.to_bytes_hex())
.map(String::try_from)
.transpose()
.with_context(|| {
"Can not convert certificate to message: can not encode the ancillary verifier data"
Expand Down Expand Up @@ -694,4 +692,45 @@ mod tests {
}
}
}

mod multi_signature_encoding {
use crate::test::double::fake_data;

use super::*;

#[test]
fn concatenation_multi_signature_is_encoded_as_json_hex() {
let certificate = fake_data::certificate("hash");
let expected_json_hex = match &certificate.signature {
CertificateSignature::MultiSignature(_, signature) => {
signature.to_json_hex().unwrap()
}
_ => panic!("expected a multi-signature certificate"),
};

let message = CertificateMessage::try_from(certificate).unwrap();

assert_eq!(expected_json_hex, message.multi_signature);
}

#[cfg(feature = "future_snark")]
#[test]
fn snark_multi_signature_is_encoded_as_bytes_hex() {
let snark_signature = fake_data::snark_aggregate_signature();
let expected_bytes_hex = snark_signature.to_bytes_hex().unwrap();
let mut certificate = fake_data::certificate("hash");
let signed_entity_type = match &certificate.signature {
CertificateSignature::MultiSignature(signed_entity_type, _) => {
signed_entity_type.clone()
}
_ => panic!("expected a multi-signature certificate"),
};
certificate.signature =
CertificateSignature::MultiSignature(signed_entity_type, snark_signature);

let message = CertificateMessage::try_from(certificate).unwrap();

assert_eq!(expected_bytes_hex, message.multi_signature);
}
}
}
6 changes: 6 additions & 0 deletions mithril-stm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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.2 (08-04-2026)

### Changed

- Encoded the proof bytes of the aggregate signature, the SNARK proof and the IVC proof as CBOR byte strings instead of arrays of integers, which halves their encoded size at each nesting level (the previous encoding remains decodable)

## 0.12.1 (08-03-2026)

### Changed
Expand Down
Loading
Loading