From 03af42d98d64a51f8cd14cebd8790b08cc74ae14 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 11:51:11 +0100 Subject: [PATCH 01/23] Define RingCurveData trait and BLS12-381 impl --- Cargo.toml | 2 +- src/ring_vrf_impl.rs | 173 ++++++++++++++++++++++++++++++------------- 2 files changed, 124 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7f24781..d69a2ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ scale-info = { version = "2.11", default-features = false, features = ["derive"] schnorrkel = { version = "0.11.5", default-features = false, optional = true } ark-serialize = { version = "0.5", default-features = false, features = ["derive"] } ark-scale = { version = "0.0.13", default-features = false } -ark-vrf = { version = "0.1.0", default-features = false, features = ["bandersnatch", "ring"] } +ark-vrf = { version = "0.1.1", default-features = false, features = ["bandersnatch", "ring"] } spin = { version = "0.9", default-features = false, features = ["once"], optional = true } [dev-dependencies] diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index 88533f5..9b1a574 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -7,15 +7,15 @@ use ark_scale::ArkScale; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; #[cfg(any(feature = "std", feature = "builder-params"))] use ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2; -use ark_vrf::{ring::Verifier, suites::bandersnatch}; +use ark_vrf::{ + ring::{RingSuite, Verifier}, + suites::bandersnatch, +}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use super::*; -#[cfg(any(feature = "std", feature = "no-std-prover"))] -pub(crate) const VERIFIABLE_SRS_RAW: &[u8] = include_bytes!("ring-data/srs-uncompressed.bin"); - /// The max ring that can be handled for both sign/verify for the given PCS domain size. const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: usize) -> usize { ark_vrf::ring::max_ring_size_from_pcs_domain_size::( @@ -55,13 +55,6 @@ impl RingDomainSize { pub const fn pcs_domain_size(self) -> usize { 1 << self.as_power() } - - /// All available domain sizes. - pub const ALL: [RingDomainSize; 3] = [ - RingDomainSize::Domain11, - RingDomainSize::Domain12, - RingDomainSize::Domain16, - ]; } impl Capacity for RingDomainSize { @@ -70,30 +63,109 @@ impl Capacity for RingDomainSize { } } -/// Ring builder params binary data. -/// Only available with the `builder-params` feature. -#[cfg(any(feature = "std", feature = "builder-params"))] -mod ring_params { - pub const DOMAIN_11_RING_BUILDER_PARAMS: &[u8] = - include_bytes!("ring-data/ring-builder-params-domain11.bin"); - pub const DOMAIN_12_RING_BUILDER_PARAMS: &[u8] = - include_bytes!("ring-data/ring-builder-params-domain12.bin"); - pub const DOMAIN_16_RING_BUILDER_PARAMS: &[u8] = - include_bytes!("ring-data/ring-builder-params-domain16.bin"); +// --------------------------------------------------------------------------- +// Traits for generic ring VRF support +// --------------------------------------------------------------------------- + +/// Trait for providing pairing-curve-specific ring data. +/// +/// All RingSuites that use the same pairing curve can share the same data provider. +/// For example, all suites using BLS12-381 (like Bandersnatch) use `Bls12_381RingData`. +pub trait RingCurveData { + /// Raw SRS data (powers of tau). + #[cfg(any(feature = "std", feature = "no-std-prover"))] + fn srs_raw() -> &'static [u8]; + + /// Ring builder params for a given domain size. + #[cfg(any(feature = "std", feature = "builder-params"))] + fn ring_builder_params(domain: RingDomainSize) -> &'static [u8]; + + /// Empty ring commitment data for a given domain size. + fn empty_ring_commitment(domain: RingDomainSize) -> &'static [u8]; } -#[cfg(any(feature = "std", feature = "builder-params"))] -use ring_params::*; - -/// Ring commitment serialized binary data. -mod ring_commitment_data { - pub const DOMAIN_11_EMPTY_RING_COMMITMENT_DATA: &[u8] = - include_bytes!("ring-data/ring-builder-domain11.bin"); - pub const DOMAIN_12_EMPTY_RING_COMMITMENT_DATA: &[u8] = - include_bytes!("ring-data/ring-builder-domain12.bin"); - pub const DOMAIN_16_EMPTY_RING_COMMITMENT_DATA: &[u8] = - include_bytes!("ring-data/ring-builder-domain16.bin"); + +/// Ring data for suites using BLS12-381 pairing (e.g., Bandersnatch curves). +pub struct Bls12_381RingData; + +impl RingCurveData for Bls12_381RingData { + #[cfg(any(feature = "std", feature = "no-std-prover"))] + fn srs_raw() -> &'static [u8] { + include_bytes!("ring-data/srs-uncompressed.bin") + } + + #[cfg(any(feature = "std", feature = "builder-params"))] + fn ring_builder_params(domain: RingDomainSize) -> &'static [u8] { + match domain { + RingDomainSize::Domain11 => { + include_bytes!("ring-data/ring-builder-params-domain11.bin") + } + RingDomainSize::Domain12 => { + include_bytes!("ring-data/ring-builder-params-domain12.bin") + } + RingDomainSize::Domain16 => { + include_bytes!("ring-data/ring-builder-params-domain16.bin") + } + } + } + + fn empty_ring_commitment(domain: RingDomainSize) -> &'static [u8] { + match domain { + RingDomainSize::Domain11 => include_bytes!("ring-data/ring-builder-domain11.bin"), + RingDomainSize::Domain12 => include_bytes!("ring-data/ring-builder-domain12.bin"), + RingDomainSize::Domain16 => include_bytes!("ring-data/ring-builder-domain16.bin"), + } + } +} + +/// Trait providing suite-specific byte array types for ring VRF operations. +/// +/// This trait defines the concrete array types used for serialized forms of +/// public keys, proofs, and signatures. Each suite specifies its own sizes. +pub trait RingSuiteTypes: RingSuite { + /// Byte array type for encoded public keys. + type EncodedPublicKey: Clone + + Eq + + PartialEq + + Encode + + Decode + + core::fmt::Debug + + TypeInfo + + MaxEncodedLen + + AsRef<[u8]> + + AsMut<[u8]> + + Default + + DecodeWithMemTracking; + + /// Byte array type for ring VRF proofs. + type RingProofBytes: Clone + + Eq + + PartialEq + + Encode + + Decode + + core::fmt::Debug + + TypeInfo + + AsRef<[u8]> + + AsMut<[u8]>; + + /// Byte array type for plain VRF signatures. + type SignatureBytes: Clone + + Eq + + PartialEq + + Encode + + Decode + + core::fmt::Debug + + TypeInfo + + AsRef<[u8]> + + AsMut<[u8]>; } -use ring_commitment_data::*; + +impl RingSuiteTypes for bandersnatch::BandersnatchSha512Ell2 { + type EncodedPublicKey = [u8; 32]; + type RingProofBytes = [u8; 788]; + type SignatureBytes = [u8; 96]; +} + +// --------------------------------------------------------------------------- const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableBandersnatchVrfInput"; @@ -135,9 +207,10 @@ fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::Rin }; cell.get_or_init(|| { - let pcs_params = - bandersnatch::PcsParams::deserialize_uncompressed_unchecked(VERIFIABLE_SRS_RAW) - .unwrap(); + let pcs_params = bandersnatch::PcsParams::deserialize_uncompressed_unchecked( + Bls12_381RingData::srs_raw(), + ) + .unwrap(); bandersnatch::RingProofParams::from_pcs_params(domain_size.size(), pcs_params).unwrap() }) } @@ -156,9 +229,10 @@ fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::Rin }; cell.call_once(|| { - let pcs_params = - bandersnatch::PcsParams::deserialize_uncompressed_unchecked(VERIFIABLE_SRS_RAW) - .unwrap(); + let pcs_params = bandersnatch::PcsParams::deserialize_uncompressed_unchecked( + Bls12_381RingData::srs_raw(), + ) + .unwrap(); bandersnatch::RingProofParams::from_pcs_params(domain_size.size(), pcs_params).unwrap() }) } @@ -168,11 +242,7 @@ fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::Rin #[cfg(any(feature = "std", feature = "builder-params"))] pub fn ring_verifier_builder_params(domain_size: RingDomainSize) -> RingBuilderParams { use ark_vrf::ring::G1Affine; - let data = match domain_size { - RingDomainSize::Domain11 => DOMAIN_11_RING_BUILDER_PARAMS, - RingDomainSize::Domain12 => DOMAIN_12_RING_BUILDER_PARAMS, - RingDomainSize::Domain16 => DOMAIN_16_RING_BUILDER_PARAMS, - }; + let data = Bls12_381RingData::ring_builder_params(domain_size); let inner = >>::deserialize_uncompressed_unchecked(data).unwrap(); ark_vrf::ring::RingBuilderPcsParams::(inner) @@ -281,11 +351,7 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { fn start_members(capacity: RingDomainSize) -> Self::Intermediate { // TODO: Optimize by caching the deserialized value; must be compatible with the WASM runtime environment. - let data = match capacity { - RingDomainSize::Domain11 => DOMAIN_11_EMPTY_RING_COMMITMENT_DATA, - RingDomainSize::Domain12 => DOMAIN_12_EMPTY_RING_COMMITMENT_DATA, - RingDomainSize::Domain16 => DOMAIN_16_EMPTY_RING_COMMITMENT_DATA, - }; + let data = Bls12_381RingData::empty_ring_commitment(capacity); MembersSet::deserialize_uncompressed_unchecked(data).unwrap() } @@ -575,7 +641,14 @@ mod builder_tests { fn generate_empty_ring_builders() { use std::io::Write; - for domain_size in RingDomainSize::ALL { + /// All available domain sizes. + const ALL: [RingDomainSize; 3] = [ + RingDomainSize::Domain11, + RingDomainSize::Domain12, + RingDomainSize::Domain16, + ]; + + for domain_size in ALL { let (builder, builder_params) = start_members_from_params(domain_size); let builder_file = format!( From ec14cd84427b096473529f6a684da38e094dcfc7 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 12:20:57 +0100 Subject: [PATCH 02/23] impl_scale for generic types --- src/ring_vrf_impl.rs | 153 +++++++++++++++++++++++++++++++------------ 1 file changed, 112 insertions(+), 41 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index 9b1a574..483a387 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -1,5 +1,5 @@ use alloc::vec; -use core::ops::Range; +use core::{marker::PhantomData, ops::Range}; pub use ark_vrf; @@ -157,14 +157,35 @@ pub trait RingSuiteTypes: RingSuite { + TypeInfo + AsRef<[u8]> + AsMut<[u8]>; + + /// Encoded size of MembersSet (Intermediate). + const MEMBERS_SET_SIZE: usize; + /// Encoded size of MembersCommitment (Members). + const MEMBERS_COMMITMENT_SIZE: usize; + /// Encoded size of StaticChunk (G1 point). + const STATIC_CHUNK_SIZE: usize; } impl RingSuiteTypes for bandersnatch::BandersnatchSha512Ell2 { type EncodedPublicKey = [u8; 32]; type RingProofBytes = [u8; 788]; type SignatureBytes = [u8; 96]; + + const MEMBERS_SET_SIZE: usize = 432; + const MEMBERS_COMMITMENT_SIZE: usize = 384; + const STATIC_CHUNK_SIZE: usize = 48; } +/// Generic ring VRF implementation parameterized over suite and curve data. +/// +/// - `S`: The ring suite (e.g., `BandersnatchSha512Ell2`) +/// - `D`: The curve data provider (e.g., `Bls12_381RingData`) +pub struct RingVrfVerifiable(PhantomData<(S, D)>); + +/// Type alias for backwards compatibility - Bandersnatch suite with BLS12-381 data. +pub type BandersnatchVrfVerifiable = + RingVrfVerifiable; + // --------------------------------------------------------------------------- const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableBandersnatchVrfInput"; @@ -175,6 +196,7 @@ const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableBandersnatchVrfInput"; pub type RingBuilderParams = ark_vrf::ring::RingBuilderPcsParams; macro_rules! impl_scale { + // Concrete type version ($type_name:ident, $encoded_size:expr) => { ark_scale::impl_scale_via_ark!($type_name); @@ -191,6 +213,35 @@ macro_rules! impl_scale { } } }; + // Generic type version - size comes from RingSuiteTypes trait + ($type_name:ident, $size_expr:expr) => { + impl Decode for $type_name { + ark_scale::impl_decode_via_ark!(); + } + + impl Encode for $type_name { + ark_scale::impl_encode_via_ark!(); + } + + impl scale::EncodeLike for $type_name {} + + impl scale::MaxEncodedLen for $type_name { + fn max_encoded_len() -> usize { + $size_expr + } + } + + impl scale_info::TypeInfo for $type_name { + type Identity = Self; + fn type_info() -> scale_info::Type { + scale_info::Type::builder() + .path(scale_info::Path::new(stringify!($type_name), module_path!())) + .composite(scale_info::build::Fields::unnamed().field(|f| { + f.ty::>().type_name(stringify!($type_name)) + })) + } + } + }; } #[cfg(feature = "std")] @@ -248,42 +299,49 @@ pub fn ring_verifier_builder_params(domain_size: RingDomainSize) -> RingBuilderP ark_vrf::ring::RingBuilderPcsParams::(inner) } -#[derive(Clone, CanonicalDeserialize, CanonicalSerialize)] -pub struct MembersSet(bandersnatch::RingVerifierKeyBuilder); +#[derive(CanonicalDeserialize, CanonicalSerialize)] +#[derive_where::derive_where(Clone)] +pub struct MembersSet(pub(crate) ark_vrf::ring::RingVerifierKeyBuilder); -impl_scale!(MembersSet, 432); +impl_scale!(MembersSet, S::MEMBERS_SET_SIZE); -impl core::fmt::Debug for MembersSet { +impl core::fmt::Debug for MembersSet { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "MembersSet") } } -impl core::cmp::PartialEq for MembersSet { +impl core::cmp::PartialEq for MembersSet { fn eq(&self, other: &Self) -> bool { self.encode() == other.encode() } } -impl core::cmp::Eq for MembersSet {} +impl core::cmp::Eq for MembersSet {} + +impl DecodeWithMemTracking for MembersSet {} -#[derive(Clone, CanonicalDeserialize, CanonicalSerialize)] -pub struct MembersCommitment(bandersnatch::RingVerifierKey); +#[derive(CanonicalDeserialize, CanonicalSerialize)] +#[derive_where::derive_where(Clone)] +pub struct MembersCommitment(pub(crate) ark_vrf::ring::RingVerifierKey); -impl_scale!(MembersCommitment, 384); +impl_scale!(MembersCommitment, S::MEMBERS_COMMITMENT_SIZE); -impl core::fmt::Debug for MembersCommitment { +impl core::fmt::Debug for MembersCommitment { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "MemberCommitment") + write!(f, "MembersCommitment") } } -impl core::cmp::PartialEq for MembersCommitment { +impl core::cmp::PartialEq for MembersCommitment { fn eq(&self, other: &Self) -> bool { self.encode() == other.encode() } } -impl core::cmp::Eq for MembersCommitment {} + +impl core::cmp::Eq for MembersCommitment {} + +impl DecodeWithMemTracking for MembersCommitment {} const PUBLIC_KEY_SIZE: usize = 32; @@ -296,33 +354,39 @@ pub struct EncodedPublicKey(pub [u8; PUBLIC_KEY_SIZE]); pub struct PublicKey(bandersnatch::AffinePoint); impl_scale!(PublicKey, PUBLIC_KEY_SIZE); -#[derive(Clone, Eq, PartialEq, Debug, CanonicalSerialize, CanonicalDeserialize)] -pub struct StaticChunk(pub ark_vrf::ring::G1Affine); -impl_scale!(StaticChunk, 48); +#[derive(CanonicalSerialize, CanonicalDeserialize)] +#[derive_where::derive_where(Clone, Debug)] +pub struct StaticChunk(pub ark_vrf::ring::G1Affine); -impl DecodeWithMemTracking for StaticChunk {} +impl_scale!(StaticChunk, S::STATIC_CHUNK_SIZE); + +impl core::cmp::PartialEq for StaticChunk { + fn eq(&self, other: &Self) -> bool { + self.encode() == other.encode() + } +} + +impl core::cmp::Eq for StaticChunk {} + +impl DecodeWithMemTracking for StaticChunk {} #[derive(CanonicalSerialize, CanonicalDeserialize)] -struct IetfVrfSignature { - output: bandersnatch::Output, - proof: bandersnatch::IetfProof, +struct IetfVrfSignature { + output: ark_vrf::Output, + proof: ark_vrf::ietf::Proof, } -const PLAIN_VRF_SIGNATURE_SIZE: usize = 96; #[derive(CanonicalSerialize, CanonicalDeserialize)] -struct RingVrfSignature { - output: bandersnatch::Output, - proof: bandersnatch::RingProof, +struct RingVrfSignature { + output: ark_vrf::Output, + proof: ark_vrf::ring::Proof, } -const RING_VRF_SIGNATURE_SIZE: usize = 788; #[inline(always)] -fn make_alias(output: &bandersnatch::Output) -> Alias { - Alias::try_from(&output.hash()[..32]).expect("Bandersnatch suite hash is 64 bytes") +fn make_alias(output: &ark_vrf::Output) -> Alias { + Alias::try_from(&output.hash()[..32]).expect("Suite hash should be at least 32 bytes") } -pub struct BandersnatchVrfVerifiable; - impl BandersnatchVrfVerifiable { fn to_public_key(value: &EncodedPublicKey) -> Result { let pt = bandersnatch::AffinePoint::deserialize_compressed(&value.0[..]).map_err(|_| ())?; @@ -339,14 +403,14 @@ impl BandersnatchVrfVerifiable { } impl GenerateVerifiable for BandersnatchVrfVerifiable { - type Members = MembersCommitment; - type Intermediate = MembersSet; + type Members = MembersCommitment; + type Intermediate = MembersSet; type Member = EncodedPublicKey; type Secret = bandersnatch::Secret; type Commitment = (RingDomainSize, u32, ArkScale); - type Proof = [u8; RING_VRF_SIGNATURE_SIZE]; - type Signature = [u8; PLAIN_VRF_SIGNATURE_SIZE]; - type StaticChunk = StaticChunk; + type Proof = ::RingProofBytes; + type Signature = ::SignatureBytes; + type StaticChunk = StaticChunk; type Capacity = RingDomainSize; fn start_members(capacity: RingDomainSize) -> Self::Intermediate { @@ -404,7 +468,10 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { let input = bandersnatch::Input::new(&input_msg[..]).expect("H2C can't fail here"); let signature = - RingVrfSignature::deserialize_compressed(proof.as_slice()).map_err(|_| ())?; + RingVrfSignature::::deserialize_compressed( + proof.as_slice(), + ) + .map_err(|_| ())?; bandersnatch::Public::verify( input, @@ -425,9 +492,9 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { let output = secret.output(input); let proof = secret.prove(input, output, b""); - let signature = IetfVrfSignature { output, proof }; + let signature = IetfVrfSignature:: { output, proof }; - let mut raw = [0u8; PLAIN_VRF_SIGNATURE_SIZE]; + let mut raw = [0u8; 96]; signature .serialize_compressed(raw.as_mut_slice()) .map_err(|_| ())?; @@ -440,7 +507,11 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { member: &Self::Member, ) -> bool { use ark_vrf::ietf::Verifier; - let Ok(signature) = IetfVrfSignature::deserialize_compressed(signature.as_slice()) else { + let Ok(signature) = + IetfVrfSignature::::deserialize_compressed( + signature.as_slice(), + ) + else { return false; }; let input_msg = [VRF_INPUT_DOMAIN, message].concat(); @@ -493,12 +564,12 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { let proof = secret.prove(input, preout, message, &ring_prover); - let signature = RingVrfSignature { + let signature = RingVrfSignature:: { output: preout, proof, }; - let mut buf = [0u8; RING_VRF_SIGNATURE_SIZE]; + let mut buf = [0u8; 788]; signature .serialize_compressed(buf.as_mut_slice()) .map_err(|_| ())?; From 27129a628f4f516ae84743ffa6e64f0130baca5f Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 13:06:52 +0100 Subject: [PATCH 03/23] Generic RingVerifiable --- src/ring_vrf_impl.rs | 175 +++++++++++++++++++++++------------- utils/generate_test_keys.rs | 6 +- 2 files changed, 118 insertions(+), 63 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index 483a387..cd6f281 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -121,13 +121,14 @@ impl RingCurveData for Bls12_381RingData { /// /// This trait defines the concrete array types used for serialized forms of /// public keys, proofs, and signatures. Each suite specifies its own sizes. -pub trait RingSuiteTypes: RingSuite { +pub trait RingSuiteTypes: RingSuite + 'static { /// Byte array type for encoded public keys. type EncodedPublicKey: Clone + Eq + PartialEq + Encode + Decode + + scale::EncodeLike + core::fmt::Debug + TypeInfo + MaxEncodedLen @@ -142,6 +143,7 @@ pub trait RingSuiteTypes: RingSuite { + PartialEq + Encode + Decode + + scale::EncodeLike + core::fmt::Debug + TypeInfo + AsRef<[u8]> @@ -153,17 +155,35 @@ pub trait RingSuiteTypes: RingSuite { + PartialEq + Encode + Decode + + scale::EncodeLike + core::fmt::Debug + TypeInfo + AsRef<[u8]> + AsMut<[u8]>; + /// Create a zero-initialized ring proof buffer. + fn zero_proof() -> Self::RingProofBytes; + + /// Create a zero-initialized signature buffer. + fn zero_signature() -> Self::SignatureBytes; + + /// Encoded size of a public key. + const PUBLIC_KEY_SIZE: usize; /// Encoded size of MembersSet (Intermediate). const MEMBERS_SET_SIZE: usize; /// Encoded size of MembersCommitment (Members). const MEMBERS_COMMITMENT_SIZE: usize; /// Encoded size of StaticChunk (G1 point). const STATIC_CHUNK_SIZE: usize; + + /// The curve data provider for this suite. + type CurveData: RingCurveData; + + /// Get cached ring proof params for this suite. + #[cfg(any(feature = "std", feature = "no-std-prover"))] + fn ring_proof_params( + domain_size: RingDomainSize, + ) -> &'static ark_vrf::ring::RingProofParams; } impl RingSuiteTypes for bandersnatch::BandersnatchSha512Ell2 { @@ -171,24 +191,32 @@ impl RingSuiteTypes for bandersnatch::BandersnatchSha512Ell2 { type RingProofBytes = [u8; 788]; type SignatureBytes = [u8; 96]; + const PUBLIC_KEY_SIZE: usize = 32; const MEMBERS_SET_SIZE: usize = 432; const MEMBERS_COMMITMENT_SIZE: usize = 384; const STATIC_CHUNK_SIZE: usize = 48; -} -/// Generic ring VRF implementation parameterized over suite and curve data. -/// -/// - `S`: The ring suite (e.g., `BandersnatchSha512Ell2`) -/// - `D`: The curve data provider (e.g., `Bls12_381RingData`) -pub struct RingVrfVerifiable(PhantomData<(S, D)>); + type CurveData = Bls12_381RingData; -/// Type alias for backwards compatibility - Bandersnatch suite with BLS12-381 data. -pub type BandersnatchVrfVerifiable = - RingVrfVerifiable; + #[cfg(any(feature = "std", feature = "no-std-prover"))] + fn ring_proof_params( + domain_size: RingDomainSize, + ) -> &'static ark_vrf::ring::RingProofParams { + ring_prover_params(domain_size) + } + + fn zero_proof() -> Self::RingProofBytes { + [0u8; 788] + } + + fn zero_signature() -> Self::SignatureBytes { + [0u8; 96] + } +} // --------------------------------------------------------------------------- -const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableBandersnatchVrfInput"; +const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableVrfInput"; /// A sequence of static chunks. /// Only available with the `builder-params` feature. @@ -235,9 +263,13 @@ macro_rules! impl_scale { type Identity = Self; fn type_info() -> scale_info::Type { scale_info::Type::builder() - .path(scale_info::Path::new(stringify!($type_name), module_path!())) + .path(scale_info::Path::new( + stringify!($type_name), + module_path!(), + )) .composite(scale_info::build::Fields::unnamed().field(|f| { - f.ty::>().type_name(stringify!($type_name)) + f.ty::>() + .type_name(stringify!($type_name)) })) } } @@ -350,9 +382,19 @@ const PUBLIC_KEY_SIZE: usize = 32; )] pub struct EncodedPublicKey(pub [u8; PUBLIC_KEY_SIZE]); -#[derive(Clone, Eq, PartialEq, Debug, CanonicalSerialize, CanonicalDeserialize)] -pub struct PublicKey(bandersnatch::AffinePoint); -impl_scale!(PublicKey, PUBLIC_KEY_SIZE); +#[derive(CanonicalSerialize, CanonicalDeserialize)] +#[derive_where::derive_where(Clone, Debug)] +pub struct PublicKey(pub(crate) ark_vrf::AffinePoint); + +impl_scale!(PublicKey, S::PUBLIC_KEY_SIZE); + +impl core::cmp::PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + self.encode() == other.encode() + } +} + +impl core::cmp::Eq for PublicKey {} #[derive(CanonicalSerialize, CanonicalDeserialize)] #[derive_where::derive_where(Clone, Debug)] @@ -387,35 +429,45 @@ fn make_alias(output: &ark_vrf::Output) -> Alias { Alias::try_from(&output.hash()[..32]).expect("Suite hash should be at least 32 bytes") } -impl BandersnatchVrfVerifiable { - fn to_public_key(value: &EncodedPublicKey) -> Result { - let pt = bandersnatch::AffinePoint::deserialize_compressed(&value.0[..]).map_err(|_| ())?; - Ok(PublicKey(pt.into())) +/// Generic ring VRF implementation parameterized over the ring suite. +/// +/// The curve data provider is obtained from `S::CurveData`. +pub struct RingVrfVerifiable(PhantomData); + +impl RingVrfVerifiable { + fn to_public_key(value: &S::EncodedPublicKey) -> Result, ()> { + let pt = + ark_vrf::AffinePoint::::deserialize_compressed(value.as_ref()).map_err(|_| ())?; + Ok(PublicKey(pt)) } - fn to_encoded_public_key(value: &PublicKey) -> EncodedPublicKey { - let mut bytes = [0u8; PUBLIC_KEY_SIZE]; + fn to_encoded_public_key(value: &PublicKey) -> S::EncodedPublicKey { + let mut bytes = S::EncodedPublicKey::default(); value.using_encoded(|encoded| { - bytes.copy_from_slice(encoded); + bytes.as_mut().copy_from_slice(encoded); }); - EncodedPublicKey(bytes) + bytes } } -impl GenerateVerifiable for BandersnatchVrfVerifiable { - type Members = MembersCommitment; - type Intermediate = MembersSet; - type Member = EncodedPublicKey; - type Secret = bandersnatch::Secret; - type Commitment = (RingDomainSize, u32, ArkScale); - type Proof = ::RingProofBytes; - type Signature = ::SignatureBytes; - type StaticChunk = StaticChunk; +impl GenerateVerifiable for RingVrfVerifiable { + type Members = MembersCommitment; + type Intermediate = MembersSet; + type Member = S::EncodedPublicKey; + type Secret = ark_vrf::Secret; + type Commitment = ( + RingDomainSize, + u32, + ArkScale>, + ); + type Proof = S::RingProofBytes; + type Signature = S::SignatureBytes; + type StaticChunk = StaticChunk; type Capacity = RingDomainSize; fn start_members(capacity: RingDomainSize) -> Self::Intermediate { // TODO: Optimize by caching the deserialized value; must be compatible with the WASM runtime environment. - let data = Bls12_381RingData::empty_ring_commitment(capacity); + let data = S::CurveData::empty_ring_commitment(capacity); MembersSet::deserialize_uncompressed_unchecked(data).unwrap() } @@ -461,19 +513,18 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { ) -> Result { // This doesn't require the whole kzg. Thus is more appropriate if used on-chain // Is a bit slower as it requires to recompute piop_params, but still in the order of ms - let ring_verifier = - bandersnatch::RingProofParams::verifier_no_context(members.0.clone(), capacity.size()); + let ring_verifier = ark_vrf::ring::RingProofParams::::verifier_no_context( + members.0.clone(), + capacity.size(), + ); let input_msg = [VRF_INPUT_DOMAIN, context].concat(); - let input = bandersnatch::Input::new(&input_msg[..]).expect("H2C can't fail here"); + let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let signature = - RingVrfSignature::::deserialize_compressed( - proof.as_slice(), - ) - .map_err(|_| ())?; + RingVrfSignature::::deserialize_compressed(proof.as_ref()).map_err(|_| ())?; - bandersnatch::Public::verify( + ark_vrf::Public::::verify( input, signature.output, message, @@ -488,15 +539,15 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { fn sign(secret: &Self::Secret, message: &[u8]) -> Result { use ark_vrf::ietf::Prover; let input_msg = [VRF_INPUT_DOMAIN, message].concat(); - let input = bandersnatch::Input::new(&input_msg[..]).expect("H2C can't fail here"); + let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let output = secret.output(input); let proof = secret.prove(input, output, b""); - let signature = IetfVrfSignature:: { output, proof }; + let signature = IetfVrfSignature:: { output, proof }; - let mut raw = [0u8; 96]; + let mut raw = S::zero_signature(); signature - .serialize_compressed(raw.as_mut_slice()) + .serialize_compressed(raw.as_mut()) .map_err(|_| ())?; Ok(raw) } @@ -507,19 +558,16 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { member: &Self::Member, ) -> bool { use ark_vrf::ietf::Verifier; - let Ok(signature) = - IetfVrfSignature::::deserialize_compressed( - signature.as_slice(), - ) + let Ok(signature) = IetfVrfSignature::::deserialize_compressed(signature.as_ref()) else { return false; }; let input_msg = [VRF_INPUT_DOMAIN, message].concat(); - let input = bandersnatch::Input::new(&input_msg[..]).expect("H2C can't fail here"); + let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let Ok(member) = Self::to_public_key(member) else { return false; }; - let public = bandersnatch::Public::from(member.0); + let public = ark_vrf::Public::::from(member.0); public .verify(input, signature.output, b"", &signature.proof) .is_ok() @@ -537,7 +585,7 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { let member = Self::to_public_key(member)?; let member_idx = pks.iter().position(|&m| m == member.0).ok_or(())?; let member_idx = member_idx as u32; - let prover_key = ring_prover_params(capacity).prover_key(&pks[..]); + let prover_key = S::ring_proof_params(capacity).prover_key(&pks[..]); Ok((capacity, member_idx, prover_key.into())) } @@ -550,7 +598,7 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { ) -> Result<(Self::Proof, Alias), ()> { use ark_vrf::ring::Prover; let (domain_size, prover_idx, prover_key) = commitment; - let params = ring_prover_params(domain_size); + let params = S::ring_proof_params(domain_size); if prover_idx >= params.max_ring_size() as u32 { return Err(()); } @@ -558,20 +606,20 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { let ring_prover = params.prover(prover_key.0, prover_idx as usize); let input_msg = [VRF_INPUT_DOMAIN, context].concat(); - let input = bandersnatch::Input::new(&input_msg[..]).expect("H2C can't fail here"); + let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let preout = secret.output(input); let alias = make_alias(&preout); let proof = secret.prove(input, preout, message, &ring_prover); - let signature = RingVrfSignature:: { + let signature = RingVrfSignature:: { output: preout, proof, }; - let mut buf = [0u8; 788]; + let mut buf = S::zero_proof(); signature - .serialize_compressed(buf.as_mut_slice()) + .serialize_compressed(buf.as_mut()) .map_err(|_| ())?; Ok((buf, alias)) @@ -579,7 +627,7 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { fn alias_in_context(secret: &Self::Secret, context: &[u8]) -> Result { let input_msg = [VRF_INPUT_DOMAIN, context].concat(); - let input = bandersnatch::Input::new(&input_msg[..]).expect("H2C can't fail here"); + let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let output = secret.output(input); let alias = make_alias(&output); Ok(alias) @@ -590,6 +638,9 @@ impl GenerateVerifiable for BandersnatchVrfVerifiable { } } +/// Bandersnatch ring VRF Verifiable (BandersnatchSha512Ell2 suite). +pub type BandersnatchVrfVerifiable = RingVrfVerifiable; + #[cfg(test)] mod tests { use super::*; @@ -606,7 +657,7 @@ mod tests { #[test] fn test_is_member_valid_invalid() { - let invalid_member = EncodedPublicKey([0; 32]); + let invalid_member = [0u8; 32]; assert!(!BandersnatchVrfVerifiable::is_member_valid(&invalid_member)); } @@ -624,6 +675,10 @@ mod builder_tests { use super::*; use ark_vrf::{ring::SrsLookup, suites::bandersnatch::BandersnatchSha512Ell2}; + // Type aliases for Bandersnatch-specific generic types + type MembersSet = super::MembersSet; + type MembersCommitment = super::MembersCommitment; + type PublicKey = super::PublicKey; type RingBuilderPcsParams = ark_vrf::ring::RingBuilderPcsParams; /// Macro to generate test functions for all implemented domain sizes. diff --git a/utils/generate_test_keys.rs b/utils/generate_test_keys.rs index cdefb62..09cfd86 100644 --- a/utils/generate_test_keys.rs +++ b/utils/generate_test_keys.rs @@ -25,7 +25,7 @@ fn print_byte_array(name: &str, data: &[u8]) { println!(); } -fn validate_keys(member: &verifiable::ring_vrf_impl::EncodedPublicKey, message: &[u8], signature: &[u8; 96]) { +fn validate_keys(member: &[u8; 32], message: &[u8], signature: &[u8; 96]) { let is_valid = BandersnatchVrfVerifiable::verify_signature(signature, message, member); if is_valid { @@ -53,7 +53,7 @@ fn main() { let signature = BandersnatchVrfVerifiable::sign(&secret, &message).unwrap(); - print_byte_array("TEST_PUBLIC_KEY", &member.0); + print_byte_array("TEST_PUBLIC_KEY", &member); print_byte_array("TEST_VRF_SIGNATURE", &signature); for i in 0..2 { @@ -61,7 +61,7 @@ fn main() { rng.fill_bytes(&mut voucher_entropy); let voucher_secret = BandersnatchVrfVerifiable::new_secret(voucher_entropy); let voucher_member = BandersnatchVrfVerifiable::member_from_secret(&voucher_secret); - print_byte_array(VOUCHER_NAMES[i], &voucher_member.0); + print_byte_array(VOUCHER_NAMES[i], &voucher_member); } validate_keys(&member, &message, &signature); From 7b5789dcd28839f0cd10b6d1ecbbb4686ca74b11 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 13:17:02 +0100 Subject: [PATCH 04/23] Some refactory --- src/ring_vrf_impl.rs | 94 ++++++++++---------------------------------- 1 file changed, 21 insertions(+), 73 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index cd6f281..3f4651e 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -161,12 +161,6 @@ pub trait RingSuiteTypes: RingSuite + 'static { + AsRef<[u8]> + AsMut<[u8]>; - /// Create a zero-initialized ring proof buffer. - fn zero_proof() -> Self::RingProofBytes; - - /// Create a zero-initialized signature buffer. - fn zero_signature() -> Self::SignatureBytes; - /// Encoded size of a public key. const PUBLIC_KEY_SIZE: usize; /// Encoded size of MembersSet (Intermediate). @@ -184,6 +178,12 @@ pub trait RingSuiteTypes: RingSuite + 'static { fn ring_proof_params( domain_size: RingDomainSize, ) -> &'static ark_vrf::ring::RingProofParams; + + /// Create a zero-initialized ring proof buffer. + fn zero_proof() -> Self::RingProofBytes; + + /// Create a zero-initialized signature buffer. + fn zero_signature() -> Self::SignatureBytes; } impl RingSuiteTypes for bandersnatch::BandersnatchSha512Ell2 { @@ -223,24 +223,7 @@ const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableVrfInput"; #[cfg(any(feature = "std", feature = "builder-params"))] pub type RingBuilderParams = ark_vrf::ring::RingBuilderPcsParams; -macro_rules! impl_scale { - // Concrete type version - ($type_name:ident, $encoded_size:expr) => { - ark_scale::impl_scale_via_ark!($type_name); - - impl scale::MaxEncodedLen for $type_name { - fn max_encoded_len() -> usize { - $encoded_size - } - } - - impl scale_info::TypeInfo for $type_name { - type Identity = [u8; $encoded_size]; - fn type_info() -> scale_info::Type { - Self::Identity::type_info() - } - } - }; +macro_rules! impl_common_traits { // Generic type version - size comes from RingSuiteTypes trait ($type_name:ident, $size_expr:expr) => { impl Decode for $type_name { @@ -273,6 +256,16 @@ macro_rules! impl_scale { })) } } + + impl core::cmp::PartialEq for $type_name { + fn eq(&self, other: &Self) -> bool { + self.encode() == other.encode() + } + } + + impl core::cmp::Eq for $type_name {} + + impl DecodeWithMemTracking for $type_name {} }; } @@ -335,7 +328,7 @@ pub fn ring_verifier_builder_params(domain_size: RingDomainSize) -> RingBuilderP #[derive_where::derive_where(Clone)] pub struct MembersSet(pub(crate) ark_vrf::ring::RingVerifierKeyBuilder); -impl_scale!(MembersSet, S::MEMBERS_SET_SIZE); +impl_common_traits!(MembersSet, S::MEMBERS_SET_SIZE); impl core::fmt::Debug for MembersSet { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -343,21 +336,11 @@ impl core::fmt::Debug for MembersSet { } } -impl core::cmp::PartialEq for MembersSet { - fn eq(&self, other: &Self) -> bool { - self.encode() == other.encode() - } -} - -impl core::cmp::Eq for MembersSet {} - -impl DecodeWithMemTracking for MembersSet {} - #[derive(CanonicalDeserialize, CanonicalSerialize)] #[derive_where::derive_where(Clone)] pub struct MembersCommitment(pub(crate) ark_vrf::ring::RingVerifierKey); -impl_scale!(MembersCommitment, S::MEMBERS_COMMITMENT_SIZE); +impl_common_traits!(MembersCommitment, S::MEMBERS_COMMITMENT_SIZE); impl core::fmt::Debug for MembersCommitment { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -365,52 +348,17 @@ impl core::fmt::Debug for MembersCommitment { } } -impl core::cmp::PartialEq for MembersCommitment { - fn eq(&self, other: &Self) -> bool { - self.encode() == other.encode() - } -} - -impl core::cmp::Eq for MembersCommitment {} - -impl DecodeWithMemTracking for MembersCommitment {} - -const PUBLIC_KEY_SIZE: usize = 32; - -#[derive( - Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, DecodeWithMemTracking, -)] -pub struct EncodedPublicKey(pub [u8; PUBLIC_KEY_SIZE]); - #[derive(CanonicalSerialize, CanonicalDeserialize)] #[derive_where::derive_where(Clone, Debug)] pub struct PublicKey(pub(crate) ark_vrf::AffinePoint); -impl_scale!(PublicKey, S::PUBLIC_KEY_SIZE); - -impl core::cmp::PartialEq for PublicKey { - fn eq(&self, other: &Self) -> bool { - self.encode() == other.encode() - } -} - -impl core::cmp::Eq for PublicKey {} +impl_common_traits!(PublicKey, S::PUBLIC_KEY_SIZE); #[derive(CanonicalSerialize, CanonicalDeserialize)] #[derive_where::derive_where(Clone, Debug)] pub struct StaticChunk(pub ark_vrf::ring::G1Affine); -impl_scale!(StaticChunk, S::STATIC_CHUNK_SIZE); - -impl core::cmp::PartialEq for StaticChunk { - fn eq(&self, other: &Self) -> bool { - self.encode() == other.encode() - } -} - -impl core::cmp::Eq for StaticChunk {} - -impl DecodeWithMemTracking for StaticChunk {} +impl_common_traits!(StaticChunk, S::STATIC_CHUNK_SIZE); #[derive(CanonicalSerialize, CanonicalDeserialize)] struct IetfVrfSignature { From 03eef5860b1679ea58e27f34cfc1fdc598809966 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 15:53:28 +0100 Subject: [PATCH 05/23] Everything should be generic now --- src/ring_vrf_impl.rs | 152 +++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index 3f4651e..81c312a 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -5,8 +5,6 @@ pub use ark_vrf; use ark_scale::ArkScale; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -#[cfg(any(feature = "std", feature = "builder-params"))] -use ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2; use ark_vrf::{ ring::{RingSuite, Verifier}, suites::bandersnatch, @@ -17,10 +15,8 @@ use scale_info::TypeInfo; use super::*; /// The max ring that can be handled for both sign/verify for the given PCS domain size. -const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: usize) -> usize { - ark_vrf::ring::max_ring_size_from_pcs_domain_size::( - pcs_domain_size, - ) +const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: usize) -> usize { + ark_vrf::ring::max_ring_size_from_pcs_domain_size::(pcs_domain_size) } /// Concrete domain sizes for the PCS (Polynomial Commitment Scheme). @@ -57,9 +53,15 @@ impl RingDomainSize { } } -impl Capacity for RingDomainSize { +#[derive(Clone, Copy, Encode, Decode, TypeInfo, DecodeWithMemTracking)] +pub struct RingSize { + dom_size: RingDomainSize, + _phantom: PhantomData, +} + +impl Capacity for RingSize { fn size(&self) -> usize { - max_ring_size_from_pcs_domain_size(self.pcs_domain_size()) + max_ring_size_from_pcs_domain_size::(self.dom_size.pcs_domain_size()) } } @@ -87,6 +89,59 @@ pub trait RingCurveData { /// Ring data for suites using BLS12-381 pairing (e.g., Bandersnatch curves). pub struct Bls12_381RingData; +#[cfg(feature = "std")] +fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::RingProofParams { + use std::sync::OnceLock; + static CELL_11: OnceLock = OnceLock::new(); + static CELL_12: OnceLock = OnceLock::new(); + static CELL_16: OnceLock = OnceLock::new(); + + let cell = match domain_size { + RingDomainSize::Domain11 => &CELL_11, + RingDomainSize::Domain12 => &CELL_12, + RingDomainSize::Domain16 => &CELL_16, + }; + cell.get_or_init(|| ring_prover_params2(domain_size)) +} + +#[cfg(all(not(feature = "std"), feature = "no-std-prover"))] +fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::RingProofParams { + use spin::Once; + static CELL_11: Once = Once::new(); + static CELL_12: Once = Once::new(); + static CELL_16: Once = Once::new(); + + let cell = match domain_size { + RingDomainSize::Domain11 => &CELL_11, + RingDomainSize::Domain12 => &CELL_12, + RingDomainSize::Domain16 => &CELL_16, + }; + cell.call_once(|| ring_prover_params2(domain_size)) +} + +#[cfg(any(feature = "std", feature = "no-std-prover"))] +pub fn ring_prover_params2( + domain_size: RingDomainSize, +) -> ark_vrf::ring::RingProofParams { + let pcs_params = + ark_vrf::ring::PcsParams::::deserialize_uncompressed_unchecked(S::CurveData::srs_raw()) + .expect("TODO"); + let ring_size = max_ring_size_from_pcs_domain_size::(domain_size.pcs_domain_size()); + ark_vrf::ring::RingProofParams::::from_pcs_params(ring_size, pcs_params).unwrap() +} + +/// Get ring builder params for the given domain size. +/// Only available with the `builder-params` or `std` features. +#[cfg(any(feature = "std", feature = "builder-params"))] +pub fn ring_verifier_builder_params( + domain_size: RingDomainSize, +) -> ark_vrf::ring::RingBuilderPcsParams { + use ark_vrf::ring::G1Affine; + let data = Bls12_381RingData::ring_builder_params(domain_size); + let inner = >>::deserialize_uncompressed_unchecked(data).unwrap(); + ark_vrf::ring::RingBuilderPcsParams::(inner) +} + impl RingCurveData for Bls12_381RingData { #[cfg(any(feature = "std", feature = "no-std-prover"))] fn srs_raw() -> &'static [u8] { @@ -218,10 +273,10 @@ impl RingSuiteTypes for bandersnatch::BandersnatchSha512Ell2 { const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableVrfInput"; -/// A sequence of static chunks. -/// Only available with the `builder-params` feature. -#[cfg(any(feature = "std", feature = "builder-params"))] -pub type RingBuilderParams = ark_vrf::ring::RingBuilderPcsParams; +// /// A sequence of static chunks. +// /// Only available with the `builder-params` feature. +// #[cfg(any(feature = "std", feature = "builder-params"))] +// pub type RingBuilderParams = ark_vrf::ring::RingBuilderPcsParams; macro_rules! impl_common_traits { // Generic type version - size comes from RingSuiteTypes trait @@ -269,61 +324,6 @@ macro_rules! impl_common_traits { }; } -#[cfg(feature = "std")] -fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::RingProofParams { - use std::sync::OnceLock; - static CELL_11: OnceLock = OnceLock::new(); - static CELL_12: OnceLock = OnceLock::new(); - static CELL_16: OnceLock = OnceLock::new(); - - let cell = match domain_size { - RingDomainSize::Domain11 => &CELL_11, - RingDomainSize::Domain12 => &CELL_12, - RingDomainSize::Domain16 => &CELL_16, - }; - - cell.get_or_init(|| { - let pcs_params = bandersnatch::PcsParams::deserialize_uncompressed_unchecked( - Bls12_381RingData::srs_raw(), - ) - .unwrap(); - bandersnatch::RingProofParams::from_pcs_params(domain_size.size(), pcs_params).unwrap() - }) -} - -#[cfg(all(not(feature = "std"), feature = "no-std-prover"))] -fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::RingProofParams { - use spin::Once; - static CELL_11: Once = Once::new(); - static CELL_12: Once = Once::new(); - static CELL_16: Once = Once::new(); - - let cell = match domain_size { - RingDomainSize::Domain11 => &CELL_11, - RingDomainSize::Domain12 => &CELL_12, - RingDomainSize::Domain16 => &CELL_16, - }; - - cell.call_once(|| { - let pcs_params = bandersnatch::PcsParams::deserialize_uncompressed_unchecked( - Bls12_381RingData::srs_raw(), - ) - .unwrap(); - bandersnatch::RingProofParams::from_pcs_params(domain_size.size(), pcs_params).unwrap() - }) -} - -/// Get ring builder params for the given domain size. -/// Only available with the `builder-params` or `std` features. -#[cfg(any(feature = "std", feature = "builder-params"))] -pub fn ring_verifier_builder_params(domain_size: RingDomainSize) -> RingBuilderParams { - use ark_vrf::ring::G1Affine; - let data = Bls12_381RingData::ring_builder_params(domain_size); - let inner = - >>::deserialize_uncompressed_unchecked(data).unwrap(); - ark_vrf::ring::RingBuilderPcsParams::(inner) -} - #[derive(CanonicalDeserialize, CanonicalSerialize)] #[derive_where::derive_where(Clone)] pub struct MembersSet(pub(crate) ark_vrf::ring::RingVerifierKeyBuilder); @@ -404,18 +404,18 @@ impl GenerateVerifiable for RingVrfVerifiable { type Member = S::EncodedPublicKey; type Secret = ark_vrf::Secret; type Commitment = ( - RingDomainSize, + Self::Capacity, u32, ArkScale>, ); type Proof = S::RingProofBytes; type Signature = S::SignatureBytes; type StaticChunk = StaticChunk; - type Capacity = RingDomainSize; + type Capacity = RingSize; - fn start_members(capacity: RingDomainSize) -> Self::Intermediate { + fn start_members(capacity: Self::Capacity) -> Self::Intermediate { // TODO: Optimize by caching the deserialized value; must be compatible with the WASM runtime environment. - let data = S::CurveData::empty_ring_commitment(capacity); + let data = S::CurveData::empty_ring_commitment(capacity.dom_size); MembersSet::deserialize_uncompressed_unchecked(data).unwrap() } @@ -453,7 +453,7 @@ impl GenerateVerifiable for RingVrfVerifiable { } fn validate( - capacity: RingDomainSize, + capacity: Self::Capacity, proof: &Self::Proof, members: &Self::Members, context: &[u8], @@ -523,7 +523,7 @@ impl GenerateVerifiable for RingVrfVerifiable { #[cfg(any(feature = "std", feature = "no-std-prover"))] fn open( - capacity: RingDomainSize, + capacity: Self::Capacity, member: &Self::Member, members: impl Iterator, ) -> Result { @@ -533,7 +533,7 @@ impl GenerateVerifiable for RingVrfVerifiable { let member = Self::to_public_key(member)?; let member_idx = pks.iter().position(|&m| m == member.0).ok_or(())?; let member_idx = member_idx as u32; - let prover_key = S::ring_proof_params(capacity).prover_key(&pks[..]); + let prover_key = S::ring_proof_params(capacity.dom_size).prover_key(&pks[..]); Ok((capacity, member_idx, prover_key.into())) } @@ -545,8 +545,8 @@ impl GenerateVerifiable for RingVrfVerifiable { message: &[u8], ) -> Result<(Self::Proof, Alias), ()> { use ark_vrf::ring::Prover; - let (domain_size, prover_idx, prover_key) = commitment; - let params = S::ring_proof_params(domain_size); + let (capacity, prover_idx, prover_key) = commitment; + let params = S::ring_proof_params(capacity.dom_size); if prover_idx >= params.max_ring_size() as u32 { return Err(()); } From 86ec6afd7934cd010d1dfff5af2cbfc8afed2966 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 16:00:21 +0100 Subject: [PATCH 06/23] Remove stuff --- src/ring_vrf_impl.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index 81c312a..acce407 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -123,9 +123,9 @@ fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::Rin pub fn ring_prover_params2( domain_size: RingDomainSize, ) -> ark_vrf::ring::RingProofParams { + let data = S::CurveData::srs_raw(); let pcs_params = - ark_vrf::ring::PcsParams::::deserialize_uncompressed_unchecked(S::CurveData::srs_raw()) - .expect("TODO"); + ark_vrf::ring::PcsParams::::deserialize_uncompressed_unchecked(data).unwrap(); let ring_size = max_ring_size_from_pcs_domain_size::(domain_size.pcs_domain_size()); ark_vrf::ring::RingProofParams::::from_pcs_params(ring_size, pcs_params).unwrap() } @@ -133,13 +133,11 @@ pub fn ring_prover_params2( /// Get ring builder params for the given domain size. /// Only available with the `builder-params` or `std` features. #[cfg(any(feature = "std", feature = "builder-params"))] -pub fn ring_verifier_builder_params( +pub fn ring_verifier_builder_params( domain_size: RingDomainSize, ) -> ark_vrf::ring::RingBuilderPcsParams { - use ark_vrf::ring::G1Affine; - let data = Bls12_381RingData::ring_builder_params(domain_size); - let inner = >>::deserialize_uncompressed_unchecked(data).unwrap(); - ark_vrf::ring::RingBuilderPcsParams::(inner) + let data = S::CurveData::ring_builder_params(domain_size); + ark_vrf::ring::RingBuilderPcsParams::::deserialize_uncompressed_unchecked(data).unwrap() } impl RingCurveData for Bls12_381RingData { @@ -225,7 +223,7 @@ pub trait RingSuiteTypes: RingSuite + 'static { /// Encoded size of StaticChunk (G1 point). const STATIC_CHUNK_SIZE: usize; - /// The curve data provider for this suite. + /// The curve static data provider for this suite. type CurveData: RingCurveData; /// Get cached ring proof params for this suite. From 8179b9e83f91f4abaec8628d5ac6fa9ea1fb6390 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 16:13:08 +0100 Subject: [PATCH 07/23] Fix tests --- src/ring_vrf_impl.rs | 71 ++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index acce407..b6306be 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -19,21 +19,16 @@ const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: usize ark_vrf::ring::max_ring_size_from_pcs_domain_size::(pcs_domain_size) } -/// Concrete domain sizes for the PCS (Polynomial Commitment Scheme). +/// Domain sizes for the PCS (Polynomial Commitment Scheme). /// -/// This determines the maximum ring size that can be supported: -/// - `Domain11`: 2^11 = 2048 domain size, supports up to 255 members -/// - `Domain12`: 2^12 = 4096 domain size, supports up to 767 members -/// - `Domain16`: 2^16 = 65536 domain size, supports up to 16127 members -#[derive( - Clone, Copy, Debug, PartialEq, Eq, Hash, Encode, Decode, TypeInfo, DecodeWithMemTracking, -)] +/// This determines the maximum ring size that can be supported for a ring suite. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode, TypeInfo, DecodeWithMemTracking)] pub enum RingDomainSize { - /// Domain size 2^11, max ring size 255 + /// Domain size 2^11 Domain11, - /// Domain size 2^12, max ring size 767 + /// Domain size 2^12 Domain12, - /// Domain size 2^16, max ring size 16127 + /// Domain size 2^16 Domain16, } @@ -53,12 +48,31 @@ impl RingDomainSize { } } +/// Ring size configuration for a specific suite. +/// +/// Wraps a [`RingDomainSize`] and computes the maximum ring capacity based on the +/// suite's curve parameters. Different suites may yield different max ring sizes +/// for the same domain size. +/// +/// Example. For the Bandersnatch suite (`BandersnatchSha512Ell2`): +/// - `Domain11`: max 255 members +/// - `Domain12`: max 767 members +/// - `Domain16`: max 16127 members #[derive(Clone, Copy, Encode, Decode, TypeInfo, DecodeWithMemTracking)] pub struct RingSize { dom_size: RingDomainSize, _phantom: PhantomData, } +impl From for RingSize { + fn from(dom_size: RingDomainSize) -> Self { + Self { + dom_size, + _phantom: PhantomData, + } + } +} + impl Capacity for RingSize { fn size(&self) -> usize { max_ring_size_from_pcs_domain_size::(self.dom_size.pcs_domain_size()) @@ -625,6 +639,7 @@ mod builder_tests { type MembersSet = super::MembersSet; type MembersCommitment = super::MembersCommitment; type PublicKey = super::PublicKey; + type RingSize = super::RingSize; type RingBuilderPcsParams = ark_vrf::ring::RingBuilderPcsParams; /// Macro to generate test functions for all implemented domain sizes. @@ -750,8 +765,9 @@ mod builder_tests { } test_for_all_domains!(check_pre_constructed_ring_builder, |domain_size| { - let builder = BandersnatchVrfVerifiable::start_members(domain_size); - let builder_params = ring_verifier_builder_params(domain_size); + let ring_size = domain_size.into(); + let builder = BandersnatchVrfVerifiable::start_members(ring_size); + let builder_params = ring_verifier_builder_params::(domain_size); let (builder2, builder_params2) = start_members_from_params(domain_size); let mut buf1 = vec![]; @@ -768,12 +784,13 @@ mod builder_tests { }); test_for_all_domains!(check_precomputed_size, |domain_size| { + let ring_size = domain_size.into(); let secret = BandersnatchVrfVerifiable::new_secret([0u8; 32]); let public = BandersnatchVrfVerifiable::member_from_secret(&secret); let internal = BandersnatchVrfVerifiable::to_public_key(&public).unwrap(); assert_eq!(internal.compressed_size(), PublicKey::max_encoded_len()); - let members = BandersnatchVrfVerifiable::start_members(domain_size); + let members = BandersnatchVrfVerifiable::start_members(ring_size); assert_eq!(members.compressed_size(), MembersSet::max_encoded_len()); let commitment = BandersnatchVrfVerifiable::finish_members(members); @@ -784,6 +801,7 @@ mod builder_tests { }); test_for_all_domains!(start_push_finish, |domain_size| { + let capacity: RingSize = domain_size.into(); let alice_sec = BandersnatchVrfVerifiable::new_secret([0u8; 32]); let bob_sec = BandersnatchVrfVerifiable::new_secret([1u8; 32]); let charlie_sec = BandersnatchVrfVerifiable::new_secret([2u8; 32]); @@ -792,9 +810,9 @@ mod builder_tests { let bob = BandersnatchVrfVerifiable::member_from_secret(&bob_sec); let charlie = BandersnatchVrfVerifiable::member_from_secret(&charlie_sec); - let mut inter1 = BandersnatchVrfVerifiable::start_members(domain_size); + let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); let mut inter2 = inter1.clone(); - let builder_params = ring_verifier_builder_params(domain_size); + let builder_params = ring_verifier_builder_params::(domain_size); let get_many = |range| { (&builder_params) @@ -827,6 +845,7 @@ mod builder_tests { }); test_for_all_domains!(start_push_finish_multiple_members, |domain_size| { + let capacity: RingSize = domain_size.into(); let alice_sec = BandersnatchVrfVerifiable::new_secret([0u8; 32]); let bob_sec = BandersnatchVrfVerifiable::new_secret([1u8; 32]); let charlie_sec = BandersnatchVrfVerifiable::new_secret([2u8; 32]); @@ -836,7 +855,7 @@ mod builder_tests { let charlie = BandersnatchVrfVerifiable::member_from_secret(&charlie_sec); // First set is everyone all at once with the regular starting root. - let mut inter1 = BandersnatchVrfVerifiable::start_members(domain_size); + let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); // Second set is everyone all at once but with a starting root constructed from params. let (mut inter2, builder_params) = start_members_from_params(domain_size); @@ -848,9 +867,9 @@ mod builder_tests { }; // Third set is everyone added one by one. - let mut inter3 = BandersnatchVrfVerifiable::start_members(domain_size); + let mut inter3 = BandersnatchVrfVerifiable::start_members(capacity); // Fourth set is a single addition followed by a group addition. - let mut inter4 = BandersnatchVrfVerifiable::start_members(domain_size); + let mut inter4 = BandersnatchVrfVerifiable::start_members(capacity); // Construct the first set with all members added simultaneously. BandersnatchVrfVerifiable::push_members( @@ -906,6 +925,7 @@ mod builder_tests { test_for_all_domains!(open_validate_works, |domain_size| { use std::time::Instant; + let capacity: RingSize = domain_size.into(); let context = b"Context"; let message = b"FooBar"; @@ -926,7 +946,7 @@ mod builder_tests { let start = Instant::now(); let commitment = - BandersnatchVrfVerifiable::open(domain_size, &member, members.clone().into_iter()) + BandersnatchVrfVerifiable::open(capacity, &member, members.clone().into_iter()) .unwrap(); println!("* Open: {} ms", (Instant::now() - start).as_millis()); println!(" Commitment size: {} bytes", commitment.encode().len()); @@ -949,7 +969,7 @@ mod builder_tests { }; let start = Instant::now(); - let mut inter = BandersnatchVrfVerifiable::start_members(domain_size); + let mut inter = BandersnatchVrfVerifiable::start_members(capacity); println!( "* Start members: {} ms", (Instant::now() - start).as_millis() @@ -980,7 +1000,7 @@ mod builder_tests { let start = Instant::now(); let alias2 = - BandersnatchVrfVerifiable::validate(domain_size, &proof, &members, context, message) + BandersnatchVrfVerifiable::validate(capacity, &proof, &members, context, message) .unwrap(); println!("* Validate {} ms", (Instant::now() - start).as_millis()); assert_eq!(alias, alias2); @@ -994,12 +1014,13 @@ mod builder_tests { test_for_all_domains!(open_validate_single_vs_multiple_keys, |domain_size| { use std::time::Instant; + let capacity: RingSize = domain_size.into(); let start = Instant::now(); let _ = ring_prover_params(domain_size); println!("* KZG decode: {} ms", (Instant::now() - start).as_millis()); // Use the domain's max ring size to test at capacity - let max_members = domain_size.size(); + let max_members = capacity.size(); println!( "* Testing with {} members (max for {:?})", max_members, domain_size @@ -1025,7 +1046,7 @@ mod builder_tests { .ok_or(()) }; - let mut inter1 = BandersnatchVrfVerifiable::start_members(domain_size); + let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); let start = Instant::now(); members.iter().for_each(|member| { BandersnatchVrfVerifiable::push_members( @@ -1041,7 +1062,7 @@ mod builder_tests { (Instant::now() - start).as_millis() ); - let mut inter2 = BandersnatchVrfVerifiable::start_members(domain_size); + let mut inter2 = BandersnatchVrfVerifiable::start_members(capacity); let start = Instant::now(); BandersnatchVrfVerifiable::push_members(&mut inter2, members.iter().cloned(), get_many) From 7d0a646d1e51b8a52062070757e49356209b2280 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 16:32:40 +0100 Subject: [PATCH 08/23] Simplify --- Cargo.toml | 3 +- src/ring_vrf_impl.rs | 117 ++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d69a2ee..fae31cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ schnorrkel = { version = "0.11.5", default-features = false, optional = true } ark-serialize = { version = "0.5", default-features = false, features = ["derive"] } ark-scale = { version = "0.0.13", default-features = false } ark-vrf = { version = "0.1.1", default-features = false, features = ["bandersnatch", "ring"] } -spin = { version = "0.9", default-features = false, features = ["once"], optional = true } +spin = { version = "0.9", default-features = false, features = ["once"] } [dev-dependencies] rand = { version = "0.8", features = ["getrandom"] } @@ -51,6 +51,5 @@ schnorrkel = ["dep:schnorrkel"] # Prover for no-std environments with deterministic ring-proof. # Not for production, may be useful for testing. no-std-prover = [ - "spin", "ark-vrf/test-vectors", ] diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index b6306be..a56360b 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -5,15 +5,16 @@ pub use ark_vrf; use ark_scale::ArkScale; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_vrf::{ - ring::{RingSuite, Verifier}, - suites::bandersnatch, -}; +use ark_vrf::ring::{RingSuite, Verifier}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use super::*; +/// Bandersnatch ring VRF Verifiable (BandersnatchSha512Ell2 suite). +pub type BandersnatchVrfVerifiable = + RingVrfVerifiable; + /// The max ring that can be handled for both sign/verify for the given PCS domain size. const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: usize) -> usize { ark_vrf::ring::max_ring_size_from_pcs_domain_size::(pcs_domain_size) @@ -103,57 +104,6 @@ pub trait RingCurveData { /// Ring data for suites using BLS12-381 pairing (e.g., Bandersnatch curves). pub struct Bls12_381RingData; -#[cfg(feature = "std")] -fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::RingProofParams { - use std::sync::OnceLock; - static CELL_11: OnceLock = OnceLock::new(); - static CELL_12: OnceLock = OnceLock::new(); - static CELL_16: OnceLock = OnceLock::new(); - - let cell = match domain_size { - RingDomainSize::Domain11 => &CELL_11, - RingDomainSize::Domain12 => &CELL_12, - RingDomainSize::Domain16 => &CELL_16, - }; - cell.get_or_init(|| ring_prover_params2(domain_size)) -} - -#[cfg(all(not(feature = "std"), feature = "no-std-prover"))] -fn ring_prover_params(domain_size: RingDomainSize) -> &'static bandersnatch::RingProofParams { - use spin::Once; - static CELL_11: Once = Once::new(); - static CELL_12: Once = Once::new(); - static CELL_16: Once = Once::new(); - - let cell = match domain_size { - RingDomainSize::Domain11 => &CELL_11, - RingDomainSize::Domain12 => &CELL_12, - RingDomainSize::Domain16 => &CELL_16, - }; - cell.call_once(|| ring_prover_params2(domain_size)) -} - -#[cfg(any(feature = "std", feature = "no-std-prover"))] -pub fn ring_prover_params2( - domain_size: RingDomainSize, -) -> ark_vrf::ring::RingProofParams { - let data = S::CurveData::srs_raw(); - let pcs_params = - ark_vrf::ring::PcsParams::::deserialize_uncompressed_unchecked(data).unwrap(); - let ring_size = max_ring_size_from_pcs_domain_size::(domain_size.pcs_domain_size()); - ark_vrf::ring::RingProofParams::::from_pcs_params(ring_size, pcs_params).unwrap() -} - -/// Get ring builder params for the given domain size. -/// Only available with the `builder-params` or `std` features. -#[cfg(any(feature = "std", feature = "builder-params"))] -pub fn ring_verifier_builder_params( - domain_size: RingDomainSize, -) -> ark_vrf::ring::RingBuilderPcsParams { - let data = S::CurveData::ring_builder_params(domain_size); - ark_vrf::ring::RingBuilderPcsParams::::deserialize_uncompressed_unchecked(data).unwrap() -} - impl RingCurveData for Bls12_381RingData { #[cfg(any(feature = "std", feature = "no-std-prover"))] fn srs_raw() -> &'static [u8] { @@ -184,6 +134,28 @@ impl RingCurveData for Bls12_381RingData { } } +/// Construct ring prover params from the suite's SRS data. +#[cfg(any(feature = "std", feature = "no-std-prover"))] +pub fn make_ring_prover_params( + domain_size: RingDomainSize, +) -> ark_vrf::ring::RingProofParams { + let data = S::CurveData::srs_raw(); + let pcs_params = + ark_vrf::ring::PcsParams::::deserialize_uncompressed_unchecked(data).unwrap(); + let ring_size = max_ring_size_from_pcs_domain_size::(domain_size.pcs_domain_size()); + ark_vrf::ring::RingProofParams::::from_pcs_params(ring_size, pcs_params).unwrap() +} + +/// Get ring builder params for the given domain size. +/// Only available with the `builder-params` or `std` features. +#[cfg(any(feature = "std", feature = "builder-params"))] +pub fn ring_verifier_builder_params( + domain_size: RingDomainSize, +) -> ark_vrf::ring::RingBuilderPcsParams { + let data = S::CurveData::ring_builder_params(domain_size); + ark_vrf::ring::RingBuilderPcsParams::::deserialize_uncompressed_unchecked(data).unwrap() +} + /// Trait providing suite-specific byte array types for ring VRF operations. /// /// This trait defines the concrete array types used for serialized forms of @@ -253,7 +225,7 @@ pub trait RingSuiteTypes: RingSuite + 'static { fn zero_signature() -> Self::SignatureBytes; } -impl RingSuiteTypes for bandersnatch::BandersnatchSha512Ell2 { +impl RingSuiteTypes for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { type EncodedPublicKey = [u8; 32]; type RingProofBytes = [u8; 788]; type SignatureBytes = [u8; 96]; @@ -269,7 +241,20 @@ impl RingSuiteTypes for bandersnatch::BandersnatchSha512Ell2 { fn ring_proof_params( domain_size: RingDomainSize, ) -> &'static ark_vrf::ring::RingProofParams { - ring_prover_params(domain_size) + use ark_vrf::suites::bandersnatch; + use spin::Once; + static CELL_11: Once = Once::new(); + static CELL_12: Once = Once::new(); + static CELL_16: Once = Once::new(); + + let cell = match domain_size { + RingDomainSize::Domain11 => &CELL_11, + RingDomainSize::Domain12 => &CELL_12, + RingDomainSize::Domain16 => &CELL_16, + }; + cell.call_once(|| { + make_ring_prover_params::(domain_size) + }) } fn zero_proof() -> Self::RingProofBytes { @@ -598,9 +583,6 @@ impl GenerateVerifiable for RingVrfVerifiable { } } -/// Bandersnatch ring VRF Verifiable (BandersnatchSha512Ell2 suite). -pub type BandersnatchVrfVerifiable = RingVrfVerifiable; - #[cfg(test)] mod tests { use super::*; @@ -674,10 +656,17 @@ mod builder_tests { }; } + fn bandersnatch_ring_prover_params( + domain_size: RingDomainSize, + ) -> &'static ark_vrf::suites::bandersnatch::RingProofParams { + BandersnatchSha512Ell2::ring_proof_params(domain_size) + } + fn start_members_from_params( domain_size: RingDomainSize, ) -> (MembersSet, RingBuilderPcsParams) { - let (builder, builder_pcs_params) = ring_prover_params(domain_size).verifier_key_builder(); + let (builder, builder_pcs_params) = + bandersnatch_ring_prover_params(domain_size).verifier_key_builder(); (MembersSet(builder), builder_pcs_params) } @@ -706,7 +695,7 @@ mod builder_tests { println!("Full size: {}", buf.len()); // Use Domain16 for SRS generation (largest domain) - let full_params = ring_prover_params(RingDomainSize::Domain16); + let full_params = bandersnatch_ring_prover_params(RingDomainSize::Domain16); let mut buf = vec![]; full_params.serialize_compressed(&mut buf).unwrap(); @@ -930,7 +919,7 @@ mod builder_tests { let message = b"FooBar"; let start = Instant::now(); - let _ = ring_prover_params(domain_size); + let _ = bandersnatch_ring_prover_params(domain_size); println!( "* PCS params decode: {} ms", (Instant::now() - start).as_millis() @@ -1016,7 +1005,7 @@ mod builder_tests { let capacity: RingSize = domain_size.into(); let start = Instant::now(); - let _ = ring_prover_params(domain_size); + let _ = bandersnatch_ring_prover_params(domain_size); println!("* KZG decode: {} ms", (Instant::now() - start).as_millis()); // Use the domain's max ring size to test at capacity From bf97b6820e5a02a18074512555582313dfdc7de4 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 16:49:57 +0100 Subject: [PATCH 09/23] Further simplification --- src/ring_vrf_impl.rs | 107 +++++++++++++------------------------------ 1 file changed, 32 insertions(+), 75 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index a56360b..244cae7 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -156,50 +156,20 @@ pub fn ring_verifier_builder_params( ark_vrf::ring::RingBuilderPcsParams::::deserialize_uncompressed_unchecked(data).unwrap() } +pub trait EncodedTypesBounds: + Clone + Eq + FullCodec + core::fmt::Debug + TypeInfo + MaxEncodedLen + AsRef<[u8]> + AsMut<[u8]> +{ + const ZERO: Self; +} +impl EncodedTypesBounds for [u8; N] { + const ZERO: Self = [0; N]; +} + /// Trait providing suite-specific byte array types for ring VRF operations. /// /// This trait defines the concrete array types used for serialized forms of /// public keys, proofs, and signatures. Each suite specifies its own sizes. pub trait RingSuiteTypes: RingSuite + 'static { - /// Byte array type for encoded public keys. - type EncodedPublicKey: Clone - + Eq - + PartialEq - + Encode - + Decode - + scale::EncodeLike - + core::fmt::Debug - + TypeInfo - + MaxEncodedLen - + AsRef<[u8]> - + AsMut<[u8]> - + Default - + DecodeWithMemTracking; - - /// Byte array type for ring VRF proofs. - type RingProofBytes: Clone - + Eq - + PartialEq - + Encode - + Decode - + scale::EncodeLike - + core::fmt::Debug - + TypeInfo - + AsRef<[u8]> - + AsMut<[u8]>; - - /// Byte array type for plain VRF signatures. - type SignatureBytes: Clone - + Eq - + PartialEq - + Encode - + Decode - + scale::EncodeLike - + core::fmt::Debug - + TypeInfo - + AsRef<[u8]> - + AsMut<[u8]>; - /// Encoded size of a public key. const PUBLIC_KEY_SIZE: usize; /// Encoded size of MembersSet (Intermediate). @@ -209,6 +179,13 @@ pub trait RingSuiteTypes: RingSuite + 'static { /// Encoded size of StaticChunk (G1 point). const STATIC_CHUNK_SIZE: usize; + /// Byte array type for encoded public keys. + type PublicKeyBytes: EncodedTypesBounds; + /// Byte array type for ring VRF proofs. + type RingProofBytes: EncodedTypesBounds; + /// Byte array type for plain VRF signatures. + type SignatureBytes: EncodedTypesBounds; + /// The curve static data provider for this suite. type CurveData: RingCurveData; @@ -217,24 +194,18 @@ pub trait RingSuiteTypes: RingSuite + 'static { fn ring_proof_params( domain_size: RingDomainSize, ) -> &'static ark_vrf::ring::RingProofParams; - - /// Create a zero-initialized ring proof buffer. - fn zero_proof() -> Self::RingProofBytes; - - /// Create a zero-initialized signature buffer. - fn zero_signature() -> Self::SignatureBytes; } impl RingSuiteTypes for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { - type EncodedPublicKey = [u8; 32]; - type RingProofBytes = [u8; 788]; - type SignatureBytes = [u8; 96]; - const PUBLIC_KEY_SIZE: usize = 32; const MEMBERS_SET_SIZE: usize = 432; const MEMBERS_COMMITMENT_SIZE: usize = 384; const STATIC_CHUNK_SIZE: usize = 48; + type PublicKeyBytes = [u8; 32]; + type RingProofBytes = [u8; 788]; + type SignatureBytes = [u8; 96]; + type CurveData = Bls12_381RingData; #[cfg(any(feature = "std", feature = "no-std-prover"))] @@ -256,25 +227,8 @@ impl RingSuiteTypes for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { make_ring_prover_params::(domain_size) }) } - - fn zero_proof() -> Self::RingProofBytes { - [0u8; 788] - } - - fn zero_signature() -> Self::SignatureBytes { - [0u8; 96] - } } -// --------------------------------------------------------------------------- - -const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableVrfInput"; - -// /// A sequence of static chunks. -// /// Only available with the `builder-params` feature. -// #[cfg(any(feature = "std", feature = "builder-params"))] -// pub type RingBuilderParams = ark_vrf::ring::RingBuilderPcsParams; - macro_rules! impl_common_traits { // Generic type version - size comes from RingSuiteTypes trait ($type_name:ident, $size_expr:expr) => { @@ -379,15 +333,17 @@ fn make_alias(output: &ark_vrf::Output) -> Alias { /// The curve data provider is obtained from `S::CurveData`. pub struct RingVrfVerifiable(PhantomData); +const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableVrfInput"; + impl RingVrfVerifiable { - fn to_public_key(value: &S::EncodedPublicKey) -> Result, ()> { + fn to_public_key(value: &S::PublicKeyBytes) -> Result, ()> { let pt = ark_vrf::AffinePoint::::deserialize_compressed(value.as_ref()).map_err(|_| ())?; Ok(PublicKey(pt)) } - fn to_encoded_public_key(value: &PublicKey) -> S::EncodedPublicKey { - let mut bytes = S::EncodedPublicKey::default(); + fn to_encoded_public_key(value: &PublicKey) -> S::PublicKeyBytes { + let mut bytes = S::PublicKeyBytes::ZERO; value.using_encoded(|encoded| { bytes.as_mut().copy_from_slice(encoded); }); @@ -398,7 +354,7 @@ impl RingVrfVerifiable { impl GenerateVerifiable for RingVrfVerifiable { type Members = MembersCommitment; type Intermediate = MembersSet; - type Member = S::EncodedPublicKey; + type Member = S::PublicKeyBytes; type Secret = ark_vrf::Secret; type Commitment = ( Self::Capacity, @@ -466,8 +422,8 @@ impl GenerateVerifiable for RingVrfVerifiable { let input_msg = [VRF_INPUT_DOMAIN, context].concat(); let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); - let signature = - RingVrfSignature::::deserialize_compressed(proof.as_ref()).map_err(|_| ())?; + let signature = RingVrfSignature::::deserialize_compressed_unchecked(proof.as_ref()) + .map_err(|_| ())?; ark_vrf::Public::::verify( input, @@ -490,7 +446,7 @@ impl GenerateVerifiable for RingVrfVerifiable { let proof = secret.prove(input, output, b""); let signature = IetfVrfSignature:: { output, proof }; - let mut raw = S::zero_signature(); + let mut raw = S::SignatureBytes::ZERO; signature .serialize_compressed(raw.as_mut()) .map_err(|_| ())?; @@ -503,7 +459,8 @@ impl GenerateVerifiable for RingVrfVerifiable { member: &Self::Member, ) -> bool { use ark_vrf::ietf::Verifier; - let Ok(signature) = IetfVrfSignature::::deserialize_compressed(signature.as_ref()) + let Ok(signature) = + IetfVrfSignature::::deserialize_compressed_unchecked(signature.as_ref()) else { return false; }; @@ -562,7 +519,7 @@ impl GenerateVerifiable for RingVrfVerifiable { proof, }; - let mut buf = S::zero_proof(); + let mut buf = S::RingProofBytes::ZERO; signature .serialize_compressed(buf.as_mut()) .map_err(|_| ())?; From 3f60697b4527de52a9d2d6098b3053af1ba9998e Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 16:59:23 +0100 Subject: [PATCH 10/23] RingSuiteExt --- src/ring_vrf_impl.rs | 74 +++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index 244cae7..7801bc2 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -15,11 +15,6 @@ use super::*; pub type BandersnatchVrfVerifiable = RingVrfVerifiable; -/// The max ring that can be handled for both sign/verify for the given PCS domain size. -const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: usize) -> usize { - ark_vrf::ring::max_ring_size_from_pcs_domain_size::(pcs_domain_size) -} - /// Domain sizes for the PCS (Polynomial Commitment Scheme). /// /// This determines the maximum ring size that can be supported for a ring suite. @@ -60,12 +55,12 @@ impl RingDomainSize { /// - `Domain12`: max 767 members /// - `Domain16`: max 16127 members #[derive(Clone, Copy, Encode, Decode, TypeInfo, DecodeWithMemTracking)] -pub struct RingSize { +pub struct RingSize { dom_size: RingDomainSize, _phantom: PhantomData, } -impl From for RingSize { +impl From for RingSize { fn from(dom_size: RingDomainSize) -> Self { Self { dom_size, @@ -74,16 +69,12 @@ impl From for RingSize { } } -impl Capacity for RingSize { +impl Capacity for RingSize { fn size(&self) -> usize { max_ring_size_from_pcs_domain_size::(self.dom_size.pcs_domain_size()) } } -// --------------------------------------------------------------------------- -// Traits for generic ring VRF support -// --------------------------------------------------------------------------- - /// Trait for providing pairing-curve-specific ring data. /// /// All RingSuites that use the same pairing curve can share the same data provider. @@ -134,9 +125,14 @@ impl RingCurveData for Bls12_381RingData { } } +/// The max ring that can be handled for both sign/verify for the given PCS domain size. +const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: usize) -> usize { + ark_vrf::ring::max_ring_size_from_pcs_domain_size::(pcs_domain_size) +} + /// Construct ring prover params from the suite's SRS data. #[cfg(any(feature = "std", feature = "no-std-prover"))] -pub fn make_ring_prover_params( +pub fn make_ring_prover_params( domain_size: RingDomainSize, ) -> ark_vrf::ring::RingProofParams { let data = S::CurveData::srs_raw(); @@ -149,7 +145,7 @@ pub fn make_ring_prover_params( /// Get ring builder params for the given domain size. /// Only available with the `builder-params` or `std` features. #[cfg(any(feature = "std", feature = "builder-params"))] -pub fn ring_verifier_builder_params( +pub fn ring_verifier_builder_params( domain_size: RingDomainSize, ) -> ark_vrf::ring::RingBuilderPcsParams { let data = S::CurveData::ring_builder_params(domain_size); @@ -169,7 +165,7 @@ impl EncodedTypesBounds for [u8; N] { /// /// This trait defines the concrete array types used for serialized forms of /// public keys, proofs, and signatures. Each suite specifies its own sizes. -pub trait RingSuiteTypes: RingSuite + 'static { +pub trait RingSuiteExt: RingSuite + 'static { /// Encoded size of a public key. const PUBLIC_KEY_SIZE: usize; /// Encoded size of MembersSet (Intermediate). @@ -178,6 +174,10 @@ pub trait RingSuiteTypes: RingSuite + 'static { const MEMBERS_COMMITMENT_SIZE: usize; /// Encoded size of StaticChunk (G1 point). const STATIC_CHUNK_SIZE: usize; + /// Encoded size of RingVrfSignature + const RING_PROOF_SIZE: usize; + /// Encoded size of IetfVrfSignature + const SIGNATURE_SIZE: usize; /// Byte array type for encoded public keys. type PublicKeyBytes: EncodedTypesBounds; @@ -196,18 +196,20 @@ pub trait RingSuiteTypes: RingSuite + 'static { ) -> &'static ark_vrf::ring::RingProofParams; } -impl RingSuiteTypes for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { +impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { const PUBLIC_KEY_SIZE: usize = 32; const MEMBERS_SET_SIZE: usize = 432; const MEMBERS_COMMITMENT_SIZE: usize = 384; const STATIC_CHUNK_SIZE: usize = 48; - - type PublicKeyBytes = [u8; 32]; - type RingProofBytes = [u8; 788]; - type SignatureBytes = [u8; 96]; + const RING_PROOF_SIZE: usize = 788; + const SIGNATURE_SIZE: usize = 96; type CurveData = Bls12_381RingData; + type PublicKeyBytes = [u8; Self::PUBLIC_KEY_SIZE]; + type RingProofBytes = [u8; Self::RING_PROOF_SIZE]; + type SignatureBytes = [u8; Self::SIGNATURE_SIZE]; + #[cfg(any(feature = "std", feature = "no-std-prover"))] fn ring_proof_params( domain_size: RingDomainSize, @@ -277,11 +279,11 @@ macro_rules! impl_common_traits { #[derive(CanonicalDeserialize, CanonicalSerialize)] #[derive_where::derive_where(Clone)] -pub struct MembersSet(pub(crate) ark_vrf::ring::RingVerifierKeyBuilder); +pub struct MembersSet(pub(crate) ark_vrf::ring::RingVerifierKeyBuilder); -impl_common_traits!(MembersSet, S::MEMBERS_SET_SIZE); +impl_common_traits!(MembersSet, S::MEMBERS_SET_SIZE); -impl core::fmt::Debug for MembersSet { +impl core::fmt::Debug for MembersSet { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "MembersSet") } @@ -289,11 +291,11 @@ impl core::fmt::Debug for MembersSet { #[derive(CanonicalDeserialize, CanonicalSerialize)] #[derive_where::derive_where(Clone)] -pub struct MembersCommitment(pub(crate) ark_vrf::ring::RingVerifierKey); +pub struct MembersCommitment(pub(crate) ark_vrf::ring::RingVerifierKey); -impl_common_traits!(MembersCommitment, S::MEMBERS_COMMITMENT_SIZE); +impl_common_traits!(MembersCommitment, S::MEMBERS_COMMITMENT_SIZE); -impl core::fmt::Debug for MembersCommitment { +impl core::fmt::Debug for MembersCommitment { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "MembersCommitment") } @@ -301,41 +303,41 @@ impl core::fmt::Debug for MembersCommitment { #[derive(CanonicalSerialize, CanonicalDeserialize)] #[derive_where::derive_where(Clone, Debug)] -pub struct PublicKey(pub(crate) ark_vrf::AffinePoint); +pub struct PublicKey(pub(crate) ark_vrf::AffinePoint); -impl_common_traits!(PublicKey, S::PUBLIC_KEY_SIZE); +impl_common_traits!(PublicKey, S::PUBLIC_KEY_SIZE); #[derive(CanonicalSerialize, CanonicalDeserialize)] #[derive_where::derive_where(Clone, Debug)] -pub struct StaticChunk(pub ark_vrf::ring::G1Affine); +pub struct StaticChunk(pub ark_vrf::ring::G1Affine); -impl_common_traits!(StaticChunk, S::STATIC_CHUNK_SIZE); +impl_common_traits!(StaticChunk, S::STATIC_CHUNK_SIZE); #[derive(CanonicalSerialize, CanonicalDeserialize)] -struct IetfVrfSignature { +struct IetfVrfSignature { output: ark_vrf::Output, proof: ark_vrf::ietf::Proof, } #[derive(CanonicalSerialize, CanonicalDeserialize)] -struct RingVrfSignature { +struct RingVrfSignature { output: ark_vrf::Output, proof: ark_vrf::ring::Proof, } #[inline(always)] -fn make_alias(output: &ark_vrf::Output) -> Alias { +fn make_alias(output: &ark_vrf::Output) -> Alias { Alias::try_from(&output.hash()[..32]).expect("Suite hash should be at least 32 bytes") } /// Generic ring VRF implementation parameterized over the ring suite. /// /// The curve data provider is obtained from `S::CurveData`. -pub struct RingVrfVerifiable(PhantomData); +pub struct RingVrfVerifiable(PhantomData); const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableVrfInput"; -impl RingVrfVerifiable { +impl RingVrfVerifiable { fn to_public_key(value: &S::PublicKeyBytes) -> Result, ()> { let pt = ark_vrf::AffinePoint::::deserialize_compressed(value.as_ref()).map_err(|_| ())?; @@ -351,7 +353,7 @@ impl RingVrfVerifiable { } } -impl GenerateVerifiable for RingVrfVerifiable { +impl GenerateVerifiable for RingVrfVerifiable { type Members = MembersCommitment; type Intermediate = MembersSet; type Member = S::PublicKeyBytes; From 955a02ceec9e3826d5f3b922013b39194bad6283 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 17:56:28 +0100 Subject: [PATCH 11/23] ParamsCache type --- src/ring_vrf_impl.rs | 86 ++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs index 7801bc2..6c17cba 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring_vrf_impl.rs @@ -1,5 +1,5 @@ use alloc::vec; -use core::{marker::PhantomData, ops::Range}; +use core::{marker::PhantomData, ops::Deref, ops::Range}; pub use ark_vrf; @@ -152,6 +152,28 @@ pub fn ring_verifier_builder_params( ark_vrf::ring::RingBuilderPcsParams::::deserialize_uncompressed_unchecked(data).unwrap() } +/// Trait for caching ring proof params. +/// +/// The `Handle` associated type allows different caching strategies. +#[cfg(any(feature = "std", feature = "no-std-prover"))] +pub trait RingProofParamsCache { + /// Handle type returned by the cache. Must deref to `RingProofParams`. + type Handle: Deref>; + + /// Get or construct ring proof params for the given domain size. + fn get(domain_size: RingDomainSize) -> Self::Handle; +} + +/// No-caching implementation: always constructs new params. +#[cfg(any(feature = "std", feature = "no-std-prover"))] +impl RingProofParamsCache for () { + type Handle = alloc::boxed::Box>; + + fn get(domain_size: RingDomainSize) -> Self::Handle { + alloc::boxed::Box::new(make_ring_prover_params::(domain_size)) + } +} + pub trait EncodedTypesBounds: Clone + Eq + FullCodec + core::fmt::Debug + TypeInfo + MaxEncodedLen + AsRef<[u8]> + AsMut<[u8]> { @@ -189,11 +211,40 @@ pub trait RingSuiteExt: RingSuite + 'static { /// The curve static data provider for this suite. type CurveData: RingCurveData; - /// Get cached ring proof params for this suite. + /// Cache strategy for ring proof params. + /// + /// Use `()` for no caching (always construct), or a type generated by + /// [`impl_ring_params_cache!`] for static caching. #[cfg(any(feature = "std", feature = "no-std-prover"))] - fn ring_proof_params( - domain_size: RingDomainSize, - ) -> &'static ark_vrf::ring::RingProofParams; + type ParamsCache: RingProofParamsCache; +} + +/// Static cache for Bandersnatch ring proof params. +#[cfg(any(feature = "std", feature = "no-std-prover"))] +pub struct BandersnatchParamsCache; + +#[cfg(any(feature = "std", feature = "no-std-prover"))] +impl RingProofParamsCache + for BandersnatchParamsCache +{ + type Handle = &'static ark_vrf::ring::RingProofParams< + ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2, + >; + + fn get(domain_size: RingDomainSize) -> Self::Handle { + use ark_vrf::suites::bandersnatch; + use spin::Once; + + static D11: Once = Once::new(); + static D12: Once = Once::new(); + static D16: Once = Once::new(); + + match domain_size { + RingDomainSize::Domain11 => D11.call_once(|| make_ring_prover_params(domain_size)), + RingDomainSize::Domain12 => D12.call_once(|| make_ring_prover_params(domain_size)), + RingDomainSize::Domain16 => D16.call_once(|| make_ring_prover_params(domain_size)), + } + } } impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { @@ -211,24 +262,7 @@ impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { type SignatureBytes = [u8; Self::SIGNATURE_SIZE]; #[cfg(any(feature = "std", feature = "no-std-prover"))] - fn ring_proof_params( - domain_size: RingDomainSize, - ) -> &'static ark_vrf::ring::RingProofParams { - use ark_vrf::suites::bandersnatch; - use spin::Once; - static CELL_11: Once = Once::new(); - static CELL_12: Once = Once::new(); - static CELL_16: Once = Once::new(); - - let cell = match domain_size { - RingDomainSize::Domain11 => &CELL_11, - RingDomainSize::Domain12 => &CELL_12, - RingDomainSize::Domain16 => &CELL_16, - }; - cell.call_once(|| { - make_ring_prover_params::(domain_size) - }) - } + type ParamsCache = BandersnatchParamsCache; } macro_rules! impl_common_traits { @@ -489,7 +523,7 @@ impl GenerateVerifiable for RingVrfVerifiable { let member = Self::to_public_key(member)?; let member_idx = pks.iter().position(|&m| m == member.0).ok_or(())?; let member_idx = member_idx as u32; - let prover_key = S::ring_proof_params(capacity.dom_size).prover_key(&pks[..]); + let prover_key = S::ParamsCache::get(capacity.dom_size).prover_key(&pks[..]); Ok((capacity, member_idx, prover_key.into())) } @@ -502,7 +536,7 @@ impl GenerateVerifiable for RingVrfVerifiable { ) -> Result<(Self::Proof, Alias), ()> { use ark_vrf::ring::Prover; let (capacity, prover_idx, prover_key) = commitment; - let params = S::ring_proof_params(capacity.dom_size); + let params = S::ParamsCache::get(capacity.dom_size); if prover_idx >= params.max_ring_size() as u32 { return Err(()); } @@ -618,7 +652,7 @@ mod builder_tests { fn bandersnatch_ring_prover_params( domain_size: RingDomainSize, ) -> &'static ark_vrf::suites::bandersnatch::RingProofParams { - BandersnatchSha512Ell2::ring_proof_params(domain_size) + ::ParamsCache::get(domain_size) } fn start_members_from_params( From 99cc119440cbbf744eca7ecadb07dee0b1f9554c Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 18:13:22 +0100 Subject: [PATCH 12/23] Everything should be generic now --- src/lib.rs | 2 +- src/ring/bandersnatch.rs | 75 ++++++++++++ .../data}/ring-builder-domain11.bin | Bin .../data}/ring-builder-domain12.bin | Bin .../data}/ring-builder-domain16.bin | Bin .../data}/ring-builder-params-domain11.bin | Bin .../data}/ring-builder-params-domain12.bin | Bin .../data}/ring-builder-params-domain16.bin | Bin .../data}/srs-compressed.bin | Bin .../data}/srs-uncompressed.bin | Bin .../data}/zcash-srs-2-16-uncompressed.bin | Bin src/{ring_vrf_impl.rs => ring/mod.rs} | 92 ++------------ utils/generate_test_keys.rs | 112 +++++++++--------- 13 files changed, 140 insertions(+), 141 deletions(-) create mode 100644 src/ring/bandersnatch.rs rename src/{ring-data => ring/data}/ring-builder-domain11.bin (100%) rename src/{ring-data => ring/data}/ring-builder-domain12.bin (100%) rename src/{ring-data => ring/data}/ring-builder-domain16.bin (100%) rename src/{ring-data => ring/data}/ring-builder-params-domain11.bin (100%) rename src/{ring-data => ring/data}/ring-builder-params-domain12.bin (100%) rename src/{ring-data => ring/data}/ring-builder-params-domain16.bin (100%) rename src/{ring-data => ring/data}/srs-compressed.bin (100%) rename src/{ring-data => ring/data}/srs-uncompressed.bin (100%) rename src/{ring-data => ring/data}/zcash-srs-2-16-uncompressed.bin (100%) rename src/{ring_vrf_impl.rs => ring/mod.rs} (90%) diff --git a/src/lib.rs b/src/lib.rs index ba96653..e2d9d9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, FullCodec, MaxEn use scale_info::*; pub mod demo_impls; -pub mod ring_vrf_impl; +pub mod ring; /// Trait for capacity types used in ring operations. /// diff --git a/src/ring/bandersnatch.rs b/src/ring/bandersnatch.rs new file mode 100644 index 0000000..3ed6562 --- /dev/null +++ b/src/ring/bandersnatch.rs @@ -0,0 +1,75 @@ +use crate::ring::{Bls12_381RingData, RingSuiteExt, RingVrfVerifiable}; +use ark_vrf::suites::bandersnatch::{BandersnatchSha512Ell2, RingProofParams}; + +#[cfg(any(feature = "std", feature = "no-std-prover"))] +use crate::ring::{make_ring_prover_params, RingDomainSize, RingProofParamsCache}; + +/// Bandersnatch ring VRF Verifiable (BandersnatchSha512Ell2 suite). +pub type BandersnatchVrfVerifiable = RingVrfVerifiable; + +#[cfg(any(feature = "std", feature = "no-std-prover"))] +pub struct BandersnatchParamsCache; + +#[cfg(any(feature = "std", feature = "no-std-prover"))] +impl RingProofParamsCache for BandersnatchParamsCache { + type Handle = &'static ark_vrf::ring::RingProofParams; + + fn get(domain_size: RingDomainSize) -> Self::Handle { + use spin::Once; + static D11: Once = Once::new(); + static D12: Once = Once::new(); + static D16: Once = Once::new(); + match domain_size { + RingDomainSize::Domain11 => D11.call_once(|| make_ring_prover_params(domain_size)), + RingDomainSize::Domain12 => D12.call_once(|| make_ring_prover_params(domain_size)), + RingDomainSize::Domain16 => D16.call_once(|| make_ring_prover_params(domain_size)), + } + } +} + +impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { + const PUBLIC_KEY_SIZE: usize = 32; + const MEMBERS_SET_SIZE: usize = 432; + const MEMBERS_COMMITMENT_SIZE: usize = 384; + const STATIC_CHUNK_SIZE: usize = 48; + const RING_PROOF_SIZE: usize = 788; + const SIGNATURE_SIZE: usize = 96; + + type CurveData = Bls12_381RingData; + + type PublicKeyBytes = [u8; Self::PUBLIC_KEY_SIZE]; + type RingProofBytes = [u8; Self::RING_PROOF_SIZE]; + type SignatureBytes = [u8; Self::SIGNATURE_SIZE]; + + #[cfg(any(feature = "std", feature = "no-std-prover"))] + type ParamsCache = BandersnatchParamsCache; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::GenerateVerifiable; + + #[test] + fn test_plain_signature() { + let msg = b"asd"; + let secret = BandersnatchVrfVerifiable::new_secret([0; 32]); + let public = BandersnatchVrfVerifiable::member_from_secret(&secret); + let signature = BandersnatchVrfVerifiable::sign(&secret, msg).unwrap(); + let res = BandersnatchVrfVerifiable::verify_signature(&signature, msg, &public); + assert!(res); + } + + #[test] + fn test_is_member_valid_invalid() { + let invalid_member = [0u8; 32]; + assert!(!BandersnatchVrfVerifiable::is_member_valid(&invalid_member)); + } + + #[test] + fn test_is_member_valid_valid() { + let secret = BandersnatchVrfVerifiable::new_secret([42u8; 32]); + let valid_member = BandersnatchVrfVerifiable::member_from_secret(&secret); + assert!(BandersnatchVrfVerifiable::is_member_valid(&valid_member)); + } +} diff --git a/src/ring-data/ring-builder-domain11.bin b/src/ring/data/ring-builder-domain11.bin similarity index 100% rename from src/ring-data/ring-builder-domain11.bin rename to src/ring/data/ring-builder-domain11.bin diff --git a/src/ring-data/ring-builder-domain12.bin b/src/ring/data/ring-builder-domain12.bin similarity index 100% rename from src/ring-data/ring-builder-domain12.bin rename to src/ring/data/ring-builder-domain12.bin diff --git a/src/ring-data/ring-builder-domain16.bin b/src/ring/data/ring-builder-domain16.bin similarity index 100% rename from src/ring-data/ring-builder-domain16.bin rename to src/ring/data/ring-builder-domain16.bin diff --git a/src/ring-data/ring-builder-params-domain11.bin b/src/ring/data/ring-builder-params-domain11.bin similarity index 100% rename from src/ring-data/ring-builder-params-domain11.bin rename to src/ring/data/ring-builder-params-domain11.bin diff --git a/src/ring-data/ring-builder-params-domain12.bin b/src/ring/data/ring-builder-params-domain12.bin similarity index 100% rename from src/ring-data/ring-builder-params-domain12.bin rename to src/ring/data/ring-builder-params-domain12.bin diff --git a/src/ring-data/ring-builder-params-domain16.bin b/src/ring/data/ring-builder-params-domain16.bin similarity index 100% rename from src/ring-data/ring-builder-params-domain16.bin rename to src/ring/data/ring-builder-params-domain16.bin diff --git a/src/ring-data/srs-compressed.bin b/src/ring/data/srs-compressed.bin similarity index 100% rename from src/ring-data/srs-compressed.bin rename to src/ring/data/srs-compressed.bin diff --git a/src/ring-data/srs-uncompressed.bin b/src/ring/data/srs-uncompressed.bin similarity index 100% rename from src/ring-data/srs-uncompressed.bin rename to src/ring/data/srs-uncompressed.bin diff --git a/src/ring-data/zcash-srs-2-16-uncompressed.bin b/src/ring/data/zcash-srs-2-16-uncompressed.bin similarity index 100% rename from src/ring-data/zcash-srs-2-16-uncompressed.bin rename to src/ring/data/zcash-srs-2-16-uncompressed.bin diff --git a/src/ring_vrf_impl.rs b/src/ring/mod.rs similarity index 90% rename from src/ring_vrf_impl.rs rename to src/ring/mod.rs index 6c17cba..319e847 100644 --- a/src/ring_vrf_impl.rs +++ b/src/ring/mod.rs @@ -11,9 +11,7 @@ use scale_info::TypeInfo; use super::*; -/// Bandersnatch ring VRF Verifiable (BandersnatchSha512Ell2 suite). -pub type BandersnatchVrfVerifiable = - RingVrfVerifiable; +pub mod bandersnatch; /// Domain sizes for the PCS (Polynomial Commitment Scheme). /// @@ -98,29 +96,29 @@ pub struct Bls12_381RingData; impl RingCurveData for Bls12_381RingData { #[cfg(any(feature = "std", feature = "no-std-prover"))] fn srs_raw() -> &'static [u8] { - include_bytes!("ring-data/srs-uncompressed.bin") + include_bytes!("data/srs-uncompressed.bin") } #[cfg(any(feature = "std", feature = "builder-params"))] fn ring_builder_params(domain: RingDomainSize) -> &'static [u8] { match domain { RingDomainSize::Domain11 => { - include_bytes!("ring-data/ring-builder-params-domain11.bin") + include_bytes!("data/ring-builder-params-domain11.bin") } RingDomainSize::Domain12 => { - include_bytes!("ring-data/ring-builder-params-domain12.bin") + include_bytes!("data/ring-builder-params-domain12.bin") } RingDomainSize::Domain16 => { - include_bytes!("ring-data/ring-builder-params-domain16.bin") + include_bytes!("data/ring-builder-params-domain16.bin") } } } fn empty_ring_commitment(domain: RingDomainSize) -> &'static [u8] { match domain { - RingDomainSize::Domain11 => include_bytes!("ring-data/ring-builder-domain11.bin"), - RingDomainSize::Domain12 => include_bytes!("ring-data/ring-builder-domain12.bin"), - RingDomainSize::Domain16 => include_bytes!("ring-data/ring-builder-domain16.bin"), + RingDomainSize::Domain11 => include_bytes!("data/ring-builder-domain11.bin"), + RingDomainSize::Domain12 => include_bytes!("data/ring-builder-domain12.bin"), + RingDomainSize::Domain16 => include_bytes!("data/ring-builder-domain16.bin"), } } } @@ -219,52 +217,6 @@ pub trait RingSuiteExt: RingSuite + 'static { type ParamsCache: RingProofParamsCache; } -/// Static cache for Bandersnatch ring proof params. -#[cfg(any(feature = "std", feature = "no-std-prover"))] -pub struct BandersnatchParamsCache; - -#[cfg(any(feature = "std", feature = "no-std-prover"))] -impl RingProofParamsCache - for BandersnatchParamsCache -{ - type Handle = &'static ark_vrf::ring::RingProofParams< - ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2, - >; - - fn get(domain_size: RingDomainSize) -> Self::Handle { - use ark_vrf::suites::bandersnatch; - use spin::Once; - - static D11: Once = Once::new(); - static D12: Once = Once::new(); - static D16: Once = Once::new(); - - match domain_size { - RingDomainSize::Domain11 => D11.call_once(|| make_ring_prover_params(domain_size)), - RingDomainSize::Domain12 => D12.call_once(|| make_ring_prover_params(domain_size)), - RingDomainSize::Domain16 => D16.call_once(|| make_ring_prover_params(domain_size)), - } - } -} - -impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { - const PUBLIC_KEY_SIZE: usize = 32; - const MEMBERS_SET_SIZE: usize = 432; - const MEMBERS_COMMITMENT_SIZE: usize = 384; - const STATIC_CHUNK_SIZE: usize = 48; - const RING_PROOF_SIZE: usize = 788; - const SIGNATURE_SIZE: usize = 96; - - type CurveData = Bls12_381RingData; - - type PublicKeyBytes = [u8; Self::PUBLIC_KEY_SIZE]; - type RingProofBytes = [u8; Self::RING_PROOF_SIZE]; - type SignatureBytes = [u8; Self::SIGNATURE_SIZE]; - - #[cfg(any(feature = "std", feature = "no-std-prover"))] - type ParamsCache = BandersnatchParamsCache; -} - macro_rules! impl_common_traits { // Generic type version - size comes from RingSuiteTypes trait ($type_name:ident, $size_expr:expr) => { @@ -576,34 +528,6 @@ impl GenerateVerifiable for RingVrfVerifiable { } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_plain_signature() { - let msg = b"asd"; - let secret = BandersnatchVrfVerifiable::new_secret([0; 32]); - let public = BandersnatchVrfVerifiable::member_from_secret(&secret); - let signature = BandersnatchVrfVerifiable::sign(&secret, msg).unwrap(); - let res = BandersnatchVrfVerifiable::verify_signature(&signature, msg, &public); - assert!(res); - } - - #[test] - fn test_is_member_valid_invalid() { - let invalid_member = [0u8; 32]; - assert!(!BandersnatchVrfVerifiable::is_member_valid(&invalid_member)); - } - - #[test] - fn test_is_member_valid_valid() { - let secret = BandersnatchVrfVerifiable::new_secret([42u8; 32]); - let valid_member = BandersnatchVrfVerifiable::member_from_secret(&secret); - assert!(BandersnatchVrfVerifiable::is_member_valid(&valid_member)); - } -} - /// Tests that require the `builder-params` feature. #[cfg(all(test, feature = "builder-params"))] mod builder_tests { diff --git a/utils/generate_test_keys.rs b/utils/generate_test_keys.rs index 09cfd86..fbc79dd 100644 --- a/utils/generate_test_keys.rs +++ b/utils/generate_test_keys.rs @@ -1,68 +1,68 @@ -use verifiable::ring_vrf_impl::BandersnatchVrfVerifiable; -use verifiable::GenerateVerifiable; use rand::RngCore; +use verifiable::ring::bandersnatch::BandersnatchVrfVerifiable; +use verifiable::GenerateVerifiable; const PROOF_PREFIX: &[u8] = b"pop register using"; const VOUCHER_NAMES: [&str; 2] = ["TEST_VOUCHER_KEY_1", "TEST_VOUCHER_KEY_2"]; fn print_byte_array(name: &str, data: &[u8]) { - println!("const {} = new Uint8Array([", name); - for (i, &byte) in data.iter().enumerate() { - if i % 16 == 0 && i > 0 { - println!(); - print!(" "); - } - if i == 0 { - print!(" "); - } - print!("{:#04x}", byte); - if i < data.len() - 1 { - print!(", "); - } - } - println!(); - println!("]);"); - println!(); + println!("const {} = new Uint8Array([", name); + for (i, &byte) in data.iter().enumerate() { + if i % 16 == 0 && i > 0 { + println!(); + print!(" "); + } + if i == 0 { + print!(" "); + } + print!("{:#04x}", byte); + if i < data.len() - 1 { + print!(", "); + } + } + println!(); + println!("]);"); + println!(); } fn validate_keys(member: &[u8; 32], message: &[u8], signature: &[u8; 96]) { - let is_valid = BandersnatchVrfVerifiable::verify_signature(signature, message, member); - - if is_valid { - eprintln!("All generated keys are valid"); - } else { - eprintln!("Key validation failed"); - std::process::exit(1); - } + let is_valid = BandersnatchVrfVerifiable::verify_signature(signature, message, member); + + if is_valid { + eprintln!("All generated keys are valid"); + } else { + eprintln!("Key validation failed"); + std::process::exit(1); + } } fn main() { - let mut rng = rand::thread_rng(); - - let mut entropy = [0u8; 32]; - let mut candidate_address = [0u8; 32]; - rng.fill_bytes(&mut entropy); - rng.fill_bytes(&mut candidate_address); - - let secret = BandersnatchVrfVerifiable::new_secret(entropy); - let member = BandersnatchVrfVerifiable::member_from_secret(&secret); - - let mut message = Vec::new(); - message.extend_from_slice(PROOF_PREFIX); - message.extend_from_slice(&candidate_address); - - let signature = BandersnatchVrfVerifiable::sign(&secret, &message).unwrap(); - - print_byte_array("TEST_PUBLIC_KEY", &member); - print_byte_array("TEST_VRF_SIGNATURE", &signature); - - for i in 0..2 { - let mut voucher_entropy = [0u8; 32]; - rng.fill_bytes(&mut voucher_entropy); - let voucher_secret = BandersnatchVrfVerifiable::new_secret(voucher_entropy); - let voucher_member = BandersnatchVrfVerifiable::member_from_secret(&voucher_secret); - print_byte_array(VOUCHER_NAMES[i], &voucher_member); - } - - validate_keys(&member, &message, &signature); -} \ No newline at end of file + let mut rng = rand::thread_rng(); + + let mut entropy = [0u8; 32]; + let mut candidate_address = [0u8; 32]; + rng.fill_bytes(&mut entropy); + rng.fill_bytes(&mut candidate_address); + + let secret = BandersnatchVrfVerifiable::new_secret(entropy); + let member = BandersnatchVrfVerifiable::member_from_secret(&secret); + + let mut message = Vec::new(); + message.extend_from_slice(PROOF_PREFIX); + message.extend_from_slice(&candidate_address); + + let signature = BandersnatchVrfVerifiable::sign(&secret, &message).unwrap(); + + print_byte_array("TEST_PUBLIC_KEY", &member); + print_byte_array("TEST_VRF_SIGNATURE", &signature); + + for i in 0..2 { + let mut voucher_entropy = [0u8; 32]; + rng.fill_bytes(&mut voucher_entropy); + let voucher_secret = BandersnatchVrfVerifiable::new_secret(voucher_entropy); + let voucher_member = BandersnatchVrfVerifiable::member_from_secret(&voucher_secret); + print_byte_array(VOUCHER_NAMES[i], &voucher_member); + } + + validate_keys(&member, &message, &signature); +} From bb16d3b6a4386d80417f0ddf27fd02bcba4a6e84 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 18:25:25 +0100 Subject: [PATCH 13/23] Move tests --- src/ring/bandersnatch.rs | 464 +++++++++++++++++++++++++++++++++++++++ src/ring/mod.rs | 455 -------------------------------------- 2 files changed, 464 insertions(+), 455 deletions(-) diff --git a/src/ring/bandersnatch.rs b/src/ring/bandersnatch.rs index 3ed6562..216cc66 100644 --- a/src/ring/bandersnatch.rs +++ b/src/ring/bandersnatch.rs @@ -73,3 +73,467 @@ mod tests { assert!(BandersnatchVrfVerifiable::is_member_valid(&valid_member)); } } + +/// Tests that require the `builder-params` feature. +#[cfg(all(test, feature = "builder-params"))] +mod builder_tests { + use crate::{ + ring::{ring_verifier_builder_params, StaticChunk}, + Capacity, GenerateVerifiable, + }; + + use super::*; + use ark_scale::MaxEncodedLen; + use ark_serialize::CanonicalSerialize; + use ark_vrf::{ring::SrsLookup, suites::bandersnatch::BandersnatchSha512Ell2}; + use parity_scale_codec::Encode; + + // Type aliases for Bandersnatch-specific generic types + type MembersSet = crate::ring::MembersSet; + type MembersCommitment = crate::ring::MembersCommitment; + type PublicKey = crate::ring::PublicKey; + type RingSize = crate::ring::RingSize; + + type RingBuilderPcsParams = ark_vrf::ring::RingBuilderPcsParams; + + /// Macro to generate test functions for all implemented domain sizes. + /// + /// Usage: + /// ```ignore + /// test_for_all_domains!(test_name, |domain_size| { + /// // test body using domain_size + /// }); + /// ``` + macro_rules! test_for_all_domains { + ($test_name:ident, |$domain_size:ident| $body:block) => { + paste::paste! { + #[test] + fn [<$test_name _domain11>]() { + let $domain_size = RingDomainSize::Domain11; + $body + } + + #[test] + fn [<$test_name _domain12>]() { + let $domain_size = RingDomainSize::Domain12; + $body + } + + #[test] + fn [<$test_name _domain16>]() { + let $domain_size = RingDomainSize::Domain16; + $body + } + } + }; + } + + fn bandersnatch_ring_prover_params( + domain_size: RingDomainSize, + ) -> &'static ark_vrf::suites::bandersnatch::RingProofParams { + ::ParamsCache::get(domain_size) + } + + fn start_members_from_params( + domain_size: RingDomainSize, + ) -> (MembersSet, RingBuilderPcsParams) { + let (builder, builder_pcs_params) = + bandersnatch_ring_prover_params(domain_size).verifier_key_builder(); + (crate::ring::MembersSet(builder), builder_pcs_params) + } + + #[test] + #[ignore = "srs generator"] + fn generate_srs_from_full_zcash_srs() { + use std::fs::File; + use std::io::{Read, Write}; + + const FULL_ZCASH_SRS_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/ring-data/zcash-srs-2-16-uncompressed.bin" + ); + const SRS_COMPRESSED_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/ring-data/srs-compressed.bin" + ); + const SRS_UNCOMPRESSED_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/ring-data/srs-uncompressed.bin" + ); + + let mut buf = vec![]; + let mut file = File::open(FULL_ZCASH_SRS_FILE).unwrap(); + file.read_to_end(&mut buf).unwrap(); + println!("Full size: {}", buf.len()); + + // Use Domain16 for SRS generation (largest domain) + let full_params = bandersnatch_ring_prover_params(RingDomainSize::Domain16); + + let mut buf = vec![]; + full_params.serialize_compressed(&mut buf).unwrap(); + println!("Reduced size (compressed): {}", buf.len()); + let mut file = File::create(SRS_COMPRESSED_FILE).unwrap(); + file.write_all(&buf).unwrap(); + + let mut buf = vec![]; + full_params.serialize_uncompressed(&mut buf).unwrap(); + println!("Reduced size (uncompressed): {}", buf.len()); + let mut file = File::create(SRS_UNCOMPRESSED_FILE).unwrap(); + file.write_all(&buf).unwrap(); + } + + // Run only if there are some breaking changes in the backend crypto and binaries + // need to be re-generated. + #[test] + #[ignore = "ring builder generator - generates ring data for all domain sizes"] + fn generate_empty_ring_builders() { + use std::io::Write; + + /// All available domain sizes. + const ALL: [RingDomainSize; 3] = [ + RingDomainSize::Domain11, + RingDomainSize::Domain12, + RingDomainSize::Domain16, + ]; + + for domain_size in ALL { + let (builder, builder_params) = start_members_from_params(domain_size); + + let builder_file = format!( + "{}/src/ring-data/ring-builder-domain{}.bin", + env!("CARGO_MANIFEST_DIR"), + domain_size.as_power() + ); + let params_file = format!( + "{}/src/ring-data/ring-builder-params-domain{}.bin", + env!("CARGO_MANIFEST_DIR"), + domain_size.as_power() + ); + + let mut buf = Vec::with_capacity(builder.uncompressed_size()); + builder.serialize_uncompressed(&mut buf).unwrap(); + println!("Writing empty ring builder to: {}", builder_file); + let mut file = std::fs::File::create(&builder_file).unwrap(); + file.write_all(&buf).unwrap(); + + let mut buf = Vec::with_capacity(builder_params.0.uncompressed_size()); + builder_params.0.serialize_uncompressed(&mut buf).unwrap(); + println!("G1 len: {}", builder_params.0.len()); + println!("Writing ring builder params to: {}", params_file); + let mut file = std::fs::File::create(¶ms_file).unwrap(); + file.write_all(&buf).unwrap(); + } + } + + test_for_all_domains!(check_pre_constructed_ring_builder, |domain_size| { + let ring_size = domain_size.into(); + let builder = BandersnatchVrfVerifiable::start_members(ring_size); + let builder_params = ring_verifier_builder_params::(domain_size); + let (builder2, builder_params2) = start_members_from_params(domain_size); + + let mut buf1 = vec![]; + builder_params.0.serialize_uncompressed(&mut buf1).unwrap(); + let mut buf2 = vec![]; + builder_params2.0.serialize_uncompressed(&mut buf2).unwrap(); + assert_eq!(buf1, buf2); + + let mut buf1 = vec![]; + builder.serialize_uncompressed(&mut buf1).unwrap(); + let mut buf2 = vec![]; + builder2.serialize_uncompressed(&mut buf2).unwrap(); + assert_eq!(buf1, buf2); + }); + + test_for_all_domains!(check_precomputed_size, |domain_size| { + let ring_size = domain_size.into(); + let secret = BandersnatchVrfVerifiable::new_secret([0u8; 32]); + let public = BandersnatchVrfVerifiable::member_from_secret(&secret); + let internal = BandersnatchVrfVerifiable::to_public_key(&public).unwrap(); + assert_eq!(internal.compressed_size(), PublicKey::max_encoded_len()); + + let members = BandersnatchVrfVerifiable::start_members(ring_size); + assert_eq!(members.compressed_size(), MembersSet::max_encoded_len()); + + let commitment = BandersnatchVrfVerifiable::finish_members(members); + assert_eq!( + commitment.compressed_size(), + MembersCommitment::max_encoded_len() + ); + }); + + test_for_all_domains!(start_push_finish, |domain_size| { + let capacity: RingSize = domain_size.into(); + let alice_sec = BandersnatchVrfVerifiable::new_secret([0u8; 32]); + let bob_sec = BandersnatchVrfVerifiable::new_secret([1u8; 32]); + let charlie_sec = BandersnatchVrfVerifiable::new_secret([2u8; 32]); + + let alice = BandersnatchVrfVerifiable::member_from_secret(&alice_sec); + let bob = BandersnatchVrfVerifiable::member_from_secret(&bob_sec); + let charlie = BandersnatchVrfVerifiable::member_from_secret(&charlie_sec); + + let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); + let mut inter2 = inter1.clone(); + let builder_params = ring_verifier_builder_params::(domain_size); + + let get_many = |range| { + (&builder_params) + .lookup(range) + .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) + .ok_or(()) + }; + + BandersnatchVrfVerifiable::push_members( + &mut inter1, + [alice.clone(), bob.clone(), charlie.clone()].into_iter(), + get_many, + ) + .unwrap(); + BandersnatchVrfVerifiable::push_members(&mut inter2, [alice.clone()].into_iter(), get_many) + .unwrap(); + BandersnatchVrfVerifiable::push_members(&mut inter2, [bob.clone()].into_iter(), get_many) + .unwrap(); + BandersnatchVrfVerifiable::push_members( + &mut inter2, + [charlie.clone()].into_iter(), + get_many, + ) + .unwrap(); + assert_eq!(inter1, inter2); + + let members1 = BandersnatchVrfVerifiable::finish_members(inter1); + let members2 = BandersnatchVrfVerifiable::finish_members(inter2); + assert_eq!(members1, members2); + }); + + test_for_all_domains!(start_push_finish_multiple_members, |domain_size| { + let capacity: RingSize = domain_size.into(); + let alice_sec = BandersnatchVrfVerifiable::new_secret([0u8; 32]); + let bob_sec = BandersnatchVrfVerifiable::new_secret([1u8; 32]); + let charlie_sec = BandersnatchVrfVerifiable::new_secret([2u8; 32]); + + let alice = BandersnatchVrfVerifiable::member_from_secret(&alice_sec); + let bob = BandersnatchVrfVerifiable::member_from_secret(&bob_sec); + let charlie = BandersnatchVrfVerifiable::member_from_secret(&charlie_sec); + + // First set is everyone all at once with the regular starting root. + let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); + // Second set is everyone all at once but with a starting root constructed from params. + let (mut inter2, builder_params) = start_members_from_params(domain_size); + + let get_many = |range| { + (&builder_params) + .lookup(range) + .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) + .ok_or(()) + }; + + // Third set is everyone added one by one. + let mut inter3 = BandersnatchVrfVerifiable::start_members(capacity); + // Fourth set is a single addition followed by a group addition. + let mut inter4 = BandersnatchVrfVerifiable::start_members(capacity); + + // Construct the first set with all members added simultaneously. + BandersnatchVrfVerifiable::push_members( + &mut inter1, + [alice.clone(), bob.clone(), charlie.clone()].into_iter(), + get_many, + ) + .unwrap(); + + // Construct the second set with all members added simultaneously. + BandersnatchVrfVerifiable::push_members( + &mut inter2, + [alice.clone(), bob.clone(), charlie.clone()].into_iter(), + get_many, + ) + .unwrap(); + + // Construct the third set with all members added sequentially. + BandersnatchVrfVerifiable::push_members(&mut inter3, [alice.clone()].into_iter(), get_many) + .unwrap(); + BandersnatchVrfVerifiable::push_members(&mut inter3, [bob.clone()].into_iter(), get_many) + .unwrap(); + BandersnatchVrfVerifiable::push_members( + &mut inter3, + [charlie.clone()].into_iter(), + get_many, + ) + .unwrap(); + + // Construct the fourth set with the first member joining alone, followed by the other members joining together. + BandersnatchVrfVerifiable::push_members(&mut inter4, [alice.clone()].into_iter(), get_many) + .unwrap(); + BandersnatchVrfVerifiable::push_members( + &mut inter4, + [bob.clone(), charlie.clone()].into_iter(), + get_many, + ) + .unwrap(); + + assert_eq!(inter1, inter2); + assert_eq!(inter2, inter3); + assert_eq!(inter3, inter4); + + let members1 = BandersnatchVrfVerifiable::finish_members(inter1); + let members2 = BandersnatchVrfVerifiable::finish_members(inter2); + let members3 = BandersnatchVrfVerifiable::finish_members(inter3); + let members4 = BandersnatchVrfVerifiable::finish_members(inter4); + assert_eq!(members1, members2); + assert_eq!(members2, members3); + assert_eq!(members3, members4); + }); + + test_for_all_domains!(open_validate_works, |domain_size| { + use std::time::Instant; + + let capacity: RingSize = domain_size.into(); + let context = b"Context"; + let message = b"FooBar"; + + let start = Instant::now(); + let _ = bandersnatch_ring_prover_params(domain_size); + println!( + "* PCS params decode: {} ms", + (Instant::now() - start).as_millis() + ); + + let members: Vec<_> = (0..10) + .map(|i| { + let secret = BandersnatchVrfVerifiable::new_secret([i as u8; 32]); + BandersnatchVrfVerifiable::member_from_secret(&secret) + }) + .collect(); + let member = members[3].clone(); + + let start = Instant::now(); + let commitment = + BandersnatchVrfVerifiable::open(capacity, &member, members.clone().into_iter()) + .unwrap(); + println!("* Open: {} ms", (Instant::now() - start).as_millis()); + println!(" Commitment size: {} bytes", commitment.encode().len()); + + let secret = BandersnatchVrfVerifiable::new_secret([commitment.1 as u8; 32]); + let start = Instant::now(); + let (proof, alias) = + BandersnatchVrfVerifiable::create(commitment, &secret, context, message).unwrap(); + println!("* Create: {} ms", (Instant::now() - start).as_millis()); + println!(" Proof size: {} bytes", proof.encode().len()); + + // `builder_params` can be serialized/deserialized to be loaded when required + let (_, builder_params) = start_members_from_params(domain_size); + + let get_many = |range| { + (&builder_params) + .lookup(range) + .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) + .ok_or(()) + }; + + let start = Instant::now(); + let mut inter = BandersnatchVrfVerifiable::start_members(capacity); + println!( + "* Start members: {} ms", + (Instant::now() - start).as_millis() + ); + + let start = Instant::now(); + members.iter().for_each(|member| { + BandersnatchVrfVerifiable::push_members( + &mut inter, + [member.clone()].into_iter(), + get_many, + ) + .unwrap(); + }); + + println!( + "* Push {} members: {} ms", + members.len(), + (Instant::now() - start).as_millis() + ); + + let start = Instant::now(); + let members = BandersnatchVrfVerifiable::finish_members(inter); + println!( + "* Finish members: {} ms", + (Instant::now() - start).as_millis() + ); + + let start = Instant::now(); + let alias2 = + BandersnatchVrfVerifiable::validate(capacity, &proof, &members, context, message) + .unwrap(); + println!("* Validate {} ms", (Instant::now() - start).as_millis()); + assert_eq!(alias, alias2); + + let start = Instant::now(); + let alias3 = BandersnatchVrfVerifiable::alias_in_context(&secret, context).unwrap(); + println!("* Alias: {} ms", (Instant::now() - start).as_millis()); + assert_eq!(alias, alias3); + }); + + test_for_all_domains!(open_validate_single_vs_multiple_keys, |domain_size| { + use std::time::Instant; + + let capacity: RingSize = domain_size.into(); + let start = Instant::now(); + let _ = bandersnatch_ring_prover_params(domain_size); + println!("* KZG decode: {} ms", (Instant::now() - start).as_millis()); + + // Use the domain's max ring size to test at capacity + let max_members = capacity.size(); + println!( + "* Testing with {} members (max for {:?})", + max_members, domain_size + ); + + let members: Vec<_> = (0..max_members) + .map(|i| { + // Use a hash of the index to generate unique secrets for large rings + let mut seed = [0u8; 32]; + seed[..8].copy_from_slice(&(i as u64).to_le_bytes()); + let secret = BandersnatchVrfVerifiable::new_secret(seed); + BandersnatchVrfVerifiable::member_from_secret(&secret) + }) + .collect(); + + // `builder_params` can be serialized/deserialized to be loaded when required + let (_, builder_params) = start_members_from_params(domain_size); + + let get_many = |range| { + (&builder_params) + .lookup(range) + .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) + .ok_or(()) + }; + + let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); + let start = Instant::now(); + members.iter().for_each(|member| { + BandersnatchVrfVerifiable::push_members( + &mut inter1, + [member.clone()].into_iter(), + get_many, + ) + .unwrap(); + }); + println!( + "* Push {} members one at a time: {} ms", + members.len(), + (Instant::now() - start).as_millis() + ); + + let mut inter2 = BandersnatchVrfVerifiable::start_members(capacity); + let start = Instant::now(); + + BandersnatchVrfVerifiable::push_members(&mut inter2, members.iter().cloned(), get_many) + .unwrap(); + println!( + "* Push {} members simultaneously: {} ms", + members.len(), + (Instant::now() - start).as_millis() + ); + + assert_eq!(inter1, inter2); + }); +} diff --git a/src/ring/mod.rs b/src/ring/mod.rs index 319e847..6bc93f7 100644 --- a/src/ring/mod.rs +++ b/src/ring/mod.rs @@ -527,458 +527,3 @@ impl GenerateVerifiable for RingVrfVerifiable { Self::to_public_key(member).is_ok() } } - -/// Tests that require the `builder-params` feature. -#[cfg(all(test, feature = "builder-params"))] -mod builder_tests { - use super::*; - use ark_vrf::{ring::SrsLookup, suites::bandersnatch::BandersnatchSha512Ell2}; - - // Type aliases for Bandersnatch-specific generic types - type MembersSet = super::MembersSet; - type MembersCommitment = super::MembersCommitment; - type PublicKey = super::PublicKey; - type RingSize = super::RingSize; - type RingBuilderPcsParams = ark_vrf::ring::RingBuilderPcsParams; - - /// Macro to generate test functions for all implemented domain sizes. - /// - /// Usage: - /// ```ignore - /// test_for_all_domains!(test_name, |domain_size| { - /// // test body using domain_size - /// }); - /// ``` - macro_rules! test_for_all_domains { - ($test_name:ident, |$domain_size:ident| $body:block) => { - paste::paste! { - #[test] - fn [<$test_name _domain11>]() { - let $domain_size = RingDomainSize::Domain11; - $body - } - - #[test] - fn [<$test_name _domain12>]() { - let $domain_size = RingDomainSize::Domain12; - $body - } - - #[test] - fn [<$test_name _domain16>]() { - let $domain_size = RingDomainSize::Domain16; - $body - } - } - }; - } - - fn bandersnatch_ring_prover_params( - domain_size: RingDomainSize, - ) -> &'static ark_vrf::suites::bandersnatch::RingProofParams { - ::ParamsCache::get(domain_size) - } - - fn start_members_from_params( - domain_size: RingDomainSize, - ) -> (MembersSet, RingBuilderPcsParams) { - let (builder, builder_pcs_params) = - bandersnatch_ring_prover_params(domain_size).verifier_key_builder(); - (MembersSet(builder), builder_pcs_params) - } - - #[test] - #[ignore = "srs generator"] - fn generate_srs_from_full_zcash_srs() { - use std::fs::File; - use std::io::{Read, Write}; - - const FULL_ZCASH_SRS_FILE: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/ring-data/zcash-srs-2-16-uncompressed.bin" - ); - const SRS_COMPRESSED_FILE: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/ring-data/srs-compressed.bin" - ); - const SRS_UNCOMPRESSED_FILE: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/ring-data/srs-uncompressed.bin" - ); - - let mut buf = vec![]; - let mut file = File::open(FULL_ZCASH_SRS_FILE).unwrap(); - file.read_to_end(&mut buf).unwrap(); - println!("Full size: {}", buf.len()); - - // Use Domain16 for SRS generation (largest domain) - let full_params = bandersnatch_ring_prover_params(RingDomainSize::Domain16); - - let mut buf = vec![]; - full_params.serialize_compressed(&mut buf).unwrap(); - println!("Reduced size (compressed): {}", buf.len()); - let mut file = File::create(SRS_COMPRESSED_FILE).unwrap(); - file.write_all(&buf).unwrap(); - - let mut buf = vec![]; - full_params.serialize_uncompressed(&mut buf).unwrap(); - println!("Reduced size (uncompressed): {}", buf.len()); - let mut file = File::create(SRS_UNCOMPRESSED_FILE).unwrap(); - file.write_all(&buf).unwrap(); - } - - // Run only if there are some breaking changes in the backend crypto and binaries - // need to be re-generated. - #[test] - #[ignore = "ring builder generator - generates ring data for all domain sizes"] - fn generate_empty_ring_builders() { - use std::io::Write; - - /// All available domain sizes. - const ALL: [RingDomainSize; 3] = [ - RingDomainSize::Domain11, - RingDomainSize::Domain12, - RingDomainSize::Domain16, - ]; - - for domain_size in ALL { - let (builder, builder_params) = start_members_from_params(domain_size); - - let builder_file = format!( - "{}/src/ring-data/ring-builder-domain{}.bin", - env!("CARGO_MANIFEST_DIR"), - domain_size.as_power() - ); - let params_file = format!( - "{}/src/ring-data/ring-builder-params-domain{}.bin", - env!("CARGO_MANIFEST_DIR"), - domain_size.as_power() - ); - - let mut buf = Vec::with_capacity(builder.uncompressed_size()); - builder.serialize_uncompressed(&mut buf).unwrap(); - println!("Writing empty ring builder to: {}", builder_file); - let mut file = std::fs::File::create(&builder_file).unwrap(); - file.write_all(&buf).unwrap(); - - let mut buf = Vec::with_capacity(builder_params.0.uncompressed_size()); - builder_params.0.serialize_uncompressed(&mut buf).unwrap(); - println!("G1 len: {}", builder_params.0.len()); - println!("Writing ring builder params to: {}", params_file); - let mut file = std::fs::File::create(¶ms_file).unwrap(); - file.write_all(&buf).unwrap(); - } - } - - test_for_all_domains!(check_pre_constructed_ring_builder, |domain_size| { - let ring_size = domain_size.into(); - let builder = BandersnatchVrfVerifiable::start_members(ring_size); - let builder_params = ring_verifier_builder_params::(domain_size); - let (builder2, builder_params2) = start_members_from_params(domain_size); - - let mut buf1 = vec![]; - builder_params.0.serialize_uncompressed(&mut buf1).unwrap(); - let mut buf2 = vec![]; - builder_params2.0.serialize_uncompressed(&mut buf2).unwrap(); - assert_eq!(buf1, buf2); - - let mut buf1 = vec![]; - builder.serialize_uncompressed(&mut buf1).unwrap(); - let mut buf2 = vec![]; - builder2.serialize_uncompressed(&mut buf2).unwrap(); - assert_eq!(buf1, buf2); - }); - - test_for_all_domains!(check_precomputed_size, |domain_size| { - let ring_size = domain_size.into(); - let secret = BandersnatchVrfVerifiable::new_secret([0u8; 32]); - let public = BandersnatchVrfVerifiable::member_from_secret(&secret); - let internal = BandersnatchVrfVerifiable::to_public_key(&public).unwrap(); - assert_eq!(internal.compressed_size(), PublicKey::max_encoded_len()); - - let members = BandersnatchVrfVerifiable::start_members(ring_size); - assert_eq!(members.compressed_size(), MembersSet::max_encoded_len()); - - let commitment = BandersnatchVrfVerifiable::finish_members(members); - assert_eq!( - commitment.compressed_size(), - MembersCommitment::max_encoded_len() - ); - }); - - test_for_all_domains!(start_push_finish, |domain_size| { - let capacity: RingSize = domain_size.into(); - let alice_sec = BandersnatchVrfVerifiable::new_secret([0u8; 32]); - let bob_sec = BandersnatchVrfVerifiable::new_secret([1u8; 32]); - let charlie_sec = BandersnatchVrfVerifiable::new_secret([2u8; 32]); - - let alice = BandersnatchVrfVerifiable::member_from_secret(&alice_sec); - let bob = BandersnatchVrfVerifiable::member_from_secret(&bob_sec); - let charlie = BandersnatchVrfVerifiable::member_from_secret(&charlie_sec); - - let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); - let mut inter2 = inter1.clone(); - let builder_params = ring_verifier_builder_params::(domain_size); - - let get_many = |range| { - (&builder_params) - .lookup(range) - .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) - .ok_or(()) - }; - - BandersnatchVrfVerifiable::push_members( - &mut inter1, - [alice.clone(), bob.clone(), charlie.clone()].into_iter(), - get_many, - ) - .unwrap(); - BandersnatchVrfVerifiable::push_members(&mut inter2, [alice.clone()].into_iter(), get_many) - .unwrap(); - BandersnatchVrfVerifiable::push_members(&mut inter2, [bob.clone()].into_iter(), get_many) - .unwrap(); - BandersnatchVrfVerifiable::push_members( - &mut inter2, - [charlie.clone()].into_iter(), - get_many, - ) - .unwrap(); - assert_eq!(inter1, inter2); - - let members1 = BandersnatchVrfVerifiable::finish_members(inter1); - let members2 = BandersnatchVrfVerifiable::finish_members(inter2); - assert_eq!(members1, members2); - }); - - test_for_all_domains!(start_push_finish_multiple_members, |domain_size| { - let capacity: RingSize = domain_size.into(); - let alice_sec = BandersnatchVrfVerifiable::new_secret([0u8; 32]); - let bob_sec = BandersnatchVrfVerifiable::new_secret([1u8; 32]); - let charlie_sec = BandersnatchVrfVerifiable::new_secret([2u8; 32]); - - let alice = BandersnatchVrfVerifiable::member_from_secret(&alice_sec); - let bob = BandersnatchVrfVerifiable::member_from_secret(&bob_sec); - let charlie = BandersnatchVrfVerifiable::member_from_secret(&charlie_sec); - - // First set is everyone all at once with the regular starting root. - let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); - // Second set is everyone all at once but with a starting root constructed from params. - let (mut inter2, builder_params) = start_members_from_params(domain_size); - - let get_many = |range| { - (&builder_params) - .lookup(range) - .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) - .ok_or(()) - }; - - // Third set is everyone added one by one. - let mut inter3 = BandersnatchVrfVerifiable::start_members(capacity); - // Fourth set is a single addition followed by a group addition. - let mut inter4 = BandersnatchVrfVerifiable::start_members(capacity); - - // Construct the first set with all members added simultaneously. - BandersnatchVrfVerifiable::push_members( - &mut inter1, - [alice.clone(), bob.clone(), charlie.clone()].into_iter(), - get_many, - ) - .unwrap(); - - // Construct the second set with all members added simultaneously. - BandersnatchVrfVerifiable::push_members( - &mut inter2, - [alice.clone(), bob.clone(), charlie.clone()].into_iter(), - get_many, - ) - .unwrap(); - - // Construct the third set with all members added sequentially. - BandersnatchVrfVerifiable::push_members(&mut inter3, [alice.clone()].into_iter(), get_many) - .unwrap(); - BandersnatchVrfVerifiable::push_members(&mut inter3, [bob.clone()].into_iter(), get_many) - .unwrap(); - BandersnatchVrfVerifiable::push_members( - &mut inter3, - [charlie.clone()].into_iter(), - get_many, - ) - .unwrap(); - - // Construct the fourth set with the first member joining alone, followed by the other members joining together. - BandersnatchVrfVerifiable::push_members(&mut inter4, [alice.clone()].into_iter(), get_many) - .unwrap(); - BandersnatchVrfVerifiable::push_members( - &mut inter4, - [bob.clone(), charlie.clone()].into_iter(), - get_many, - ) - .unwrap(); - - assert_eq!(inter1, inter2); - assert_eq!(inter2, inter3); - assert_eq!(inter3, inter4); - - let members1 = BandersnatchVrfVerifiable::finish_members(inter1); - let members2 = BandersnatchVrfVerifiable::finish_members(inter2); - let members3 = BandersnatchVrfVerifiable::finish_members(inter3); - let members4 = BandersnatchVrfVerifiable::finish_members(inter4); - assert_eq!(members1, members2); - assert_eq!(members2, members3); - assert_eq!(members3, members4); - }); - - test_for_all_domains!(open_validate_works, |domain_size| { - use std::time::Instant; - - let capacity: RingSize = domain_size.into(); - let context = b"Context"; - let message = b"FooBar"; - - let start = Instant::now(); - let _ = bandersnatch_ring_prover_params(domain_size); - println!( - "* PCS params decode: {} ms", - (Instant::now() - start).as_millis() - ); - - let members: Vec<_> = (0..10) - .map(|i| { - let secret = BandersnatchVrfVerifiable::new_secret([i as u8; 32]); - BandersnatchVrfVerifiable::member_from_secret(&secret) - }) - .collect(); - let member = members[3].clone(); - - let start = Instant::now(); - let commitment = - BandersnatchVrfVerifiable::open(capacity, &member, members.clone().into_iter()) - .unwrap(); - println!("* Open: {} ms", (Instant::now() - start).as_millis()); - println!(" Commitment size: {} bytes", commitment.encode().len()); - - let secret = BandersnatchVrfVerifiable::new_secret([commitment.1 as u8; 32]); - let start = Instant::now(); - let (proof, alias) = - BandersnatchVrfVerifiable::create(commitment, &secret, context, message).unwrap(); - println!("* Create: {} ms", (Instant::now() - start).as_millis()); - println!(" Proof size: {} bytes", proof.encode().len()); - - // `builder_params` can be serialized/deserialized to be loaded when required - let (_, builder_params) = start_members_from_params(domain_size); - - let get_many = |range| { - (&builder_params) - .lookup(range) - .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) - .ok_or(()) - }; - - let start = Instant::now(); - let mut inter = BandersnatchVrfVerifiable::start_members(capacity); - println!( - "* Start members: {} ms", - (Instant::now() - start).as_millis() - ); - - let start = Instant::now(); - members.iter().for_each(|member| { - BandersnatchVrfVerifiable::push_members( - &mut inter, - [member.clone()].into_iter(), - get_many, - ) - .unwrap(); - }); - - println!( - "* Push {} members: {} ms", - members.len(), - (Instant::now() - start).as_millis() - ); - - let start = Instant::now(); - let members = BandersnatchVrfVerifiable::finish_members(inter); - println!( - "* Finish members: {} ms", - (Instant::now() - start).as_millis() - ); - - let start = Instant::now(); - let alias2 = - BandersnatchVrfVerifiable::validate(capacity, &proof, &members, context, message) - .unwrap(); - println!("* Validate {} ms", (Instant::now() - start).as_millis()); - assert_eq!(alias, alias2); - - let start = Instant::now(); - let alias3 = BandersnatchVrfVerifiable::alias_in_context(&secret, context).unwrap(); - println!("* Alias: {} ms", (Instant::now() - start).as_millis()); - assert_eq!(alias, alias3); - }); - - test_for_all_domains!(open_validate_single_vs_multiple_keys, |domain_size| { - use std::time::Instant; - - let capacity: RingSize = domain_size.into(); - let start = Instant::now(); - let _ = bandersnatch_ring_prover_params(domain_size); - println!("* KZG decode: {} ms", (Instant::now() - start).as_millis()); - - // Use the domain's max ring size to test at capacity - let max_members = capacity.size(); - println!( - "* Testing with {} members (max for {:?})", - max_members, domain_size - ); - - let members: Vec<_> = (0..max_members) - .map(|i| { - // Use a hash of the index to generate unique secrets for large rings - let mut seed = [0u8; 32]; - seed[..8].copy_from_slice(&(i as u64).to_le_bytes()); - let secret = BandersnatchVrfVerifiable::new_secret(seed); - BandersnatchVrfVerifiable::member_from_secret(&secret) - }) - .collect(); - - // `builder_params` can be serialized/deserialized to be loaded when required - let (_, builder_params) = start_members_from_params(domain_size); - - let get_many = |range| { - (&builder_params) - .lookup(range) - .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) - .ok_or(()) - }; - - let mut inter1 = BandersnatchVrfVerifiable::start_members(capacity); - let start = Instant::now(); - members.iter().for_each(|member| { - BandersnatchVrfVerifiable::push_members( - &mut inter1, - [member.clone()].into_iter(), - get_many, - ) - .unwrap(); - }); - println!( - "* Push {} members one at a time: {} ms", - members.len(), - (Instant::now() - start).as_millis() - ); - - let mut inter2 = BandersnatchVrfVerifiable::start_members(capacity); - let start = Instant::now(); - - BandersnatchVrfVerifiable::push_members(&mut inter2, members.iter().cloned(), get_many) - .unwrap(); - println!( - "* Push {} members simultaneously: {} ms", - members.len(), - (Instant::now() - start).as_millis() - ); - - assert_eq!(inter1, inter2); - }); -} From 10c286b72d202f540974a5836f5d9aa71066c670 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 18:31:30 +0100 Subject: [PATCH 14/23] More re-org --- src/{demo_impls.rs => demo.rs} | 0 src/lib.rs | 2 +- .../{ => bls12-381}/ring-builder-domain11.bin | Bin .../{ => bls12-381}/ring-builder-domain12.bin | Bin .../{ => bls12-381}/ring-builder-domain16.bin | Bin .../ring-builder-params-domain11.bin | Bin .../ring-builder-params-domain12.bin | Bin .../ring-builder-params-domain16.bin | Bin src/ring/data/{ => bls12-381}/srs-compressed.bin | Bin .../data/{ => bls12-381}/srs-uncompressed.bin | Bin .../zcash-srs-2-16-uncompressed.bin | Bin src/ring/mod.rs | 14 +++++++------- 12 files changed, 8 insertions(+), 8 deletions(-) rename src/{demo_impls.rs => demo.rs} (100%) rename src/ring/data/{ => bls12-381}/ring-builder-domain11.bin (100%) rename src/ring/data/{ => bls12-381}/ring-builder-domain12.bin (100%) rename src/ring/data/{ => bls12-381}/ring-builder-domain16.bin (100%) rename src/ring/data/{ => bls12-381}/ring-builder-params-domain11.bin (100%) rename src/ring/data/{ => bls12-381}/ring-builder-params-domain12.bin (100%) rename src/ring/data/{ => bls12-381}/ring-builder-params-domain16.bin (100%) rename src/ring/data/{ => bls12-381}/srs-compressed.bin (100%) rename src/ring/data/{ => bls12-381}/srs-uncompressed.bin (100%) rename src/ring/data/{ => bls12-381}/zcash-srs-2-16-uncompressed.bin (100%) diff --git a/src/demo_impls.rs b/src/demo.rs similarity index 100% rename from src/demo_impls.rs rename to src/demo.rs diff --git a/src/lib.rs b/src/lib.rs index e2d9d9c..1f22ddd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use core::{fmt::Debug, ops::Range}; use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, FullCodec, MaxEncodedLen}; use scale_info::*; -pub mod demo_impls; +pub mod demo; pub mod ring; /// Trait for capacity types used in ring operations. diff --git a/src/ring/data/ring-builder-domain11.bin b/src/ring/data/bls12-381/ring-builder-domain11.bin similarity index 100% rename from src/ring/data/ring-builder-domain11.bin rename to src/ring/data/bls12-381/ring-builder-domain11.bin diff --git a/src/ring/data/ring-builder-domain12.bin b/src/ring/data/bls12-381/ring-builder-domain12.bin similarity index 100% rename from src/ring/data/ring-builder-domain12.bin rename to src/ring/data/bls12-381/ring-builder-domain12.bin diff --git a/src/ring/data/ring-builder-domain16.bin b/src/ring/data/bls12-381/ring-builder-domain16.bin similarity index 100% rename from src/ring/data/ring-builder-domain16.bin rename to src/ring/data/bls12-381/ring-builder-domain16.bin diff --git a/src/ring/data/ring-builder-params-domain11.bin b/src/ring/data/bls12-381/ring-builder-params-domain11.bin similarity index 100% rename from src/ring/data/ring-builder-params-domain11.bin rename to src/ring/data/bls12-381/ring-builder-params-domain11.bin diff --git a/src/ring/data/ring-builder-params-domain12.bin b/src/ring/data/bls12-381/ring-builder-params-domain12.bin similarity index 100% rename from src/ring/data/ring-builder-params-domain12.bin rename to src/ring/data/bls12-381/ring-builder-params-domain12.bin diff --git a/src/ring/data/ring-builder-params-domain16.bin b/src/ring/data/bls12-381/ring-builder-params-domain16.bin similarity index 100% rename from src/ring/data/ring-builder-params-domain16.bin rename to src/ring/data/bls12-381/ring-builder-params-domain16.bin diff --git a/src/ring/data/srs-compressed.bin b/src/ring/data/bls12-381/srs-compressed.bin similarity index 100% rename from src/ring/data/srs-compressed.bin rename to src/ring/data/bls12-381/srs-compressed.bin diff --git a/src/ring/data/srs-uncompressed.bin b/src/ring/data/bls12-381/srs-uncompressed.bin similarity index 100% rename from src/ring/data/srs-uncompressed.bin rename to src/ring/data/bls12-381/srs-uncompressed.bin diff --git a/src/ring/data/zcash-srs-2-16-uncompressed.bin b/src/ring/data/bls12-381/zcash-srs-2-16-uncompressed.bin similarity index 100% rename from src/ring/data/zcash-srs-2-16-uncompressed.bin rename to src/ring/data/bls12-381/zcash-srs-2-16-uncompressed.bin diff --git a/src/ring/mod.rs b/src/ring/mod.rs index 6bc93f7..eb30826 100644 --- a/src/ring/mod.rs +++ b/src/ring/mod.rs @@ -96,29 +96,29 @@ pub struct Bls12_381RingData; impl RingCurveData for Bls12_381RingData { #[cfg(any(feature = "std", feature = "no-std-prover"))] fn srs_raw() -> &'static [u8] { - include_bytes!("data/srs-uncompressed.bin") + include_bytes!("data/bls12-381/srs-uncompressed.bin") } #[cfg(any(feature = "std", feature = "builder-params"))] fn ring_builder_params(domain: RingDomainSize) -> &'static [u8] { match domain { RingDomainSize::Domain11 => { - include_bytes!("data/ring-builder-params-domain11.bin") + include_bytes!("data/bls12-381/ring-builder-params-domain11.bin") } RingDomainSize::Domain12 => { - include_bytes!("data/ring-builder-params-domain12.bin") + include_bytes!("data/bls12-381/ring-builder-params-domain12.bin") } RingDomainSize::Domain16 => { - include_bytes!("data/ring-builder-params-domain16.bin") + include_bytes!("data/bls12-381/ring-builder-params-domain16.bin") } } } fn empty_ring_commitment(domain: RingDomainSize) -> &'static [u8] { match domain { - RingDomainSize::Domain11 => include_bytes!("data/ring-builder-domain11.bin"), - RingDomainSize::Domain12 => include_bytes!("data/ring-builder-domain12.bin"), - RingDomainSize::Domain16 => include_bytes!("data/ring-builder-domain16.bin"), + RingDomainSize::Domain11 => include_bytes!("data/bls12-381/ring-builder-domain11.bin"), + RingDomainSize::Domain12 => include_bytes!("data/bls12-381/ring-builder-domain12.bin"), + RingDomainSize::Domain16 => include_bytes!("data/bls12-381/ring-builder-domain16.bin"), } } } From 23748520d3415abe774e11e37c59773730d6190d Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 18:52:07 +0100 Subject: [PATCH 15/23] Codec assumptions check --- src/ring/bandersnatch.rs | 136 +++++++++++++++++++++++++++++++-------- 1 file changed, 108 insertions(+), 28 deletions(-) diff --git a/src/ring/bandersnatch.rs b/src/ring/bandersnatch.rs index 216cc66..273ed36 100644 --- a/src/ring/bandersnatch.rs +++ b/src/ring/bandersnatch.rs @@ -47,9 +47,35 @@ impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { #[cfg(test)] mod tests { + use ark_scale::MaxEncodedLen; + use ark_serialize::CanonicalSerialize; + use ark_vrf::ring::SrsLookup; + use super::*; use crate::GenerateVerifiable; + // Type aliases for Bandersnatch-specific generic types + pub type MembersSet = crate::ring::MembersSet; + pub type MembersCommitment = crate::ring::MembersCommitment; + pub type PublicKey = crate::ring::PublicKey; + pub type StaticChunk = crate::ring::StaticChunk; + + type RingBuilderPcsParams = ark_vrf::ring::RingBuilderPcsParams; + + pub fn bandersnatch_ring_prover_params( + domain_size: RingDomainSize, + ) -> &'static ark_vrf::suites::bandersnatch::RingProofParams { + ::ParamsCache::get(domain_size) + } + + pub fn start_members_from_params( + domain_size: RingDomainSize, + ) -> (MembersSet, RingBuilderPcsParams) { + let (builder, builder_pcs_params) = + bandersnatch_ring_prover_params(domain_size).verifier_key_builder(); + (crate::ring::MembersSet(builder), builder_pcs_params) + } + #[test] fn test_plain_signature() { let msg = b"asd"; @@ -72,15 +98,68 @@ mod tests { let valid_member = BandersnatchVrfVerifiable::member_from_secret(&secret); assert!(BandersnatchVrfVerifiable::is_member_valid(&valid_member)); } + + /// Verify that the size constants in `RingSuiteExt` match actual serialized sizes. + #[test] + fn codec_assumptions_check() { + use ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 as S; + + // PUBLIC_KEY_SIZE + let secret = BandersnatchVrfVerifiable::new_secret([0u8; 32]); + let public = BandersnatchVrfVerifiable::member_from_secret(&secret); + let internal = BandersnatchVrfVerifiable::to_public_key(&public).unwrap(); + assert_eq!(internal.compressed_size(), S::PUBLIC_KEY_SIZE); + assert_eq!(PublicKey::max_encoded_len(), S::PUBLIC_KEY_SIZE); + + // MEMBERS_SET_SIZE + let members_set = BandersnatchVrfVerifiable::start_members(RingDomainSize::Domain11.into()); + assert_eq!(members_set.compressed_size(), S::MEMBERS_SET_SIZE); + assert_eq!(MembersSet::max_encoded_len(), S::MEMBERS_SET_SIZE); + + // MEMBERS_COMMITMENT_SIZE + let commitment = BandersnatchVrfVerifiable::finish_members(members_set); + assert_eq!(commitment.compressed_size(), S::MEMBERS_COMMITMENT_SIZE); + assert_eq!( + MembersCommitment::max_encoded_len(), + S::MEMBERS_COMMITMENT_SIZE + ); + + // STATIC_CHUNK_SIZE + let (_, builder_params) = start_members_from_params(RingDomainSize::Domain11); + let chunks: Vec<_> = (&builder_params).lookup(0..1).unwrap(); + let chunk: StaticChunk = crate::ring::StaticChunk(chunks[0]); + assert_eq!(chunk.compressed_size(), S::STATIC_CHUNK_SIZE); + assert_eq!(StaticChunk::max_encoded_len(), S::STATIC_CHUNK_SIZE); + + // SIGNATURE_SIZE + let signature = BandersnatchVrfVerifiable::sign(&secret, b"test").unwrap(); + assert_eq!(signature.len(), S::SIGNATURE_SIZE); + + // RING_PROOF_SIZE + let members: Vec<_> = (0..3) + .map(|i| { + let s = BandersnatchVrfVerifiable::new_secret([i as u8; 32]); + BandersnatchVrfVerifiable::member_from_secret(&s) + }) + .collect(); + let member = members[0].clone(); + let commitment = BandersnatchVrfVerifiable::open( + RingDomainSize::Domain11.into(), + &member, + members.into_iter(), + ) + .unwrap(); + let prover_secret = BandersnatchVrfVerifiable::new_secret([0u8; 32]); + let (proof, _) = + BandersnatchVrfVerifiable::create(commitment, &prover_secret, b"ctx", b"msg").unwrap(); + assert_eq!(proof.len(), S::RING_PROOF_SIZE); + } } /// Tests that require the `builder-params` feature. #[cfg(all(test, feature = "builder-params"))] mod builder_tests { - use crate::{ - ring::{ring_verifier_builder_params, StaticChunk}, - Capacity, GenerateVerifiable, - }; + use crate::{ring::ring_verifier_builder_params, Capacity, GenerateVerifiable}; use super::*; use ark_scale::MaxEncodedLen; @@ -88,13 +167,12 @@ mod builder_tests { use ark_vrf::{ring::SrsLookup, suites::bandersnatch::BandersnatchSha512Ell2}; use parity_scale_codec::Encode; - // Type aliases for Bandersnatch-specific generic types - type MembersSet = crate::ring::MembersSet; - type MembersCommitment = crate::ring::MembersCommitment; - type PublicKey = crate::ring::PublicKey; - type RingSize = crate::ring::RingSize; + use tests::{ + bandersnatch_ring_prover_params, start_members_from_params, MembersCommitment, MembersSet, + PublicKey, + }; - type RingBuilderPcsParams = ark_vrf::ring::RingBuilderPcsParams; + pub type RingSize = crate::ring::RingSize; /// Macro to generate test functions for all implemented domain sizes. /// @@ -128,20 +206,6 @@ mod builder_tests { }; } - fn bandersnatch_ring_prover_params( - domain_size: RingDomainSize, - ) -> &'static ark_vrf::suites::bandersnatch::RingProofParams { - ::ParamsCache::get(domain_size) - } - - fn start_members_from_params( - domain_size: RingDomainSize, - ) -> (MembersSet, RingBuilderPcsParams) { - let (builder, builder_pcs_params) = - bandersnatch_ring_prover_params(domain_size).verifier_key_builder(); - (crate::ring::MembersSet(builder), builder_pcs_params) - } - #[test] #[ignore = "srs generator"] fn generate_srs_from_full_zcash_srs() { @@ -278,7 +342,11 @@ mod builder_tests { let get_many = |range| { (&builder_params) .lookup(range) - .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) + .map(|v| { + v.into_iter() + .map(|i| crate::ring::StaticChunk(i)) + .collect::>() + }) .ok_or(()) }; @@ -323,7 +391,11 @@ mod builder_tests { let get_many = |range| { (&builder_params) .lookup(range) - .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) + .map(|v| { + v.into_iter() + .map(|i| crate::ring::StaticChunk(i)) + .collect::>() + }) .ok_or(()) }; @@ -425,7 +497,11 @@ mod builder_tests { let get_many = |range| { (&builder_params) .lookup(range) - .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) + .map(|v| { + v.into_iter() + .map(|i| crate::ring::StaticChunk(i)) + .collect::>() + }) .ok_or(()) }; @@ -503,7 +579,11 @@ mod builder_tests { let get_many = |range| { (&builder_params) .lookup(range) - .map(|v| v.into_iter().map(|i| StaticChunk(i)).collect::>()) + .map(|v| { + v.into_iter() + .map(|i| crate::ring::StaticChunk(i)) + .collect::>() + }) .ok_or(()) }; From 07cda9e46ffdbd5e1e4df586d6821172b90fa3f3 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 18:58:12 +0100 Subject: [PATCH 16/23] One more test --- src/ring/bandersnatch.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ring/bandersnatch.rs b/src/ring/bandersnatch.rs index 273ed36..a4d4e28 100644 --- a/src/ring/bandersnatch.rs +++ b/src/ring/bandersnatch.rs @@ -52,7 +52,7 @@ mod tests { use ark_vrf::ring::SrsLookup; use super::*; - use crate::GenerateVerifiable; + use crate::{ring::RingSize, Capacity, GenerateVerifiable}; // Type aliases for Bandersnatch-specific generic types pub type MembersSet = crate::ring::MembersSet; @@ -99,6 +99,19 @@ mod tests { assert!(BandersnatchVrfVerifiable::is_member_valid(&valid_member)); } + #[test] + fn ring_size_check() { + const DOM_TO_RING_SIZE_MAP: [(RingDomainSize, usize); 3] = [ + (RingDomainSize::Domain11, 255), + (RingDomainSize::Domain12, 767), + (RingDomainSize::Domain16, 16127), + ]; + for (dom_size, exp_ring_size) in DOM_TO_RING_SIZE_MAP { + let ring_size = RingSize::::from(dom_size); + assert_eq!(ring_size.size(), exp_ring_size); + } + } + /// Verify that the size constants in `RingSuiteExt` match actual serialized sizes. #[test] fn codec_assumptions_check() { From cc381eabd9a0ce0e4e2ed72109d49feccd3da6b9 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 5 Feb 2026 19:05:36 +0100 Subject: [PATCH 17/23] Fix binary files path --- Cargo.toml | 4 +--- src/ring/bandersnatch.rs | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fae31cb..5c3c95d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,7 @@ authors = ["Parity Technologies "] repository = "https://github.com/paritytech/verifiable.git" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -exclude = ["src/ring-data/zcash-srs-2-16-uncompressed.bin"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +exclude = ["src/ring/data/bls12-381/zcash-srs-2-16-uncompressed.bin"] [dependencies] derive-where = "1.2" diff --git a/src/ring/bandersnatch.rs b/src/ring/bandersnatch.rs index a4d4e28..b8ec074 100644 --- a/src/ring/bandersnatch.rs +++ b/src/ring/bandersnatch.rs @@ -227,15 +227,15 @@ mod builder_tests { const FULL_ZCASH_SRS_FILE: &str = concat!( env!("CARGO_MANIFEST_DIR"), - "/src/ring-data/zcash-srs-2-16-uncompressed.bin" + "/src/ring/data/bls12-381/zcash-srs-2-16-uncompressed.bin" ); const SRS_COMPRESSED_FILE: &str = concat!( env!("CARGO_MANIFEST_DIR"), - "/src/ring-data/srs-compressed.bin" + "/src/ring/data/bls12-381/srs-compressed.bin" ); const SRS_UNCOMPRESSED_FILE: &str = concat!( env!("CARGO_MANIFEST_DIR"), - "/src/ring-data/srs-uncompressed.bin" + "/src/ring/data/bls12-381/srs-uncompressed.bin" ); let mut buf = vec![]; @@ -277,12 +277,12 @@ mod builder_tests { let (builder, builder_params) = start_members_from_params(domain_size); let builder_file = format!( - "{}/src/ring-data/ring-builder-domain{}.bin", + "{}/src/ring/data/bls12-381/ring-builder-domain{}.bin", env!("CARGO_MANIFEST_DIR"), domain_size.as_power() ); let params_file = format!( - "{}/src/ring-data/ring-builder-params-domain{}.bin", + "{}/src/ring/data/bls12-381/ring-builder-params-domain{}.bin", env!("CARGO_MANIFEST_DIR"), domain_size.as_power() ); From eb2d63393180f2adac82db591e98abfef9de99b7 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 17 Feb 2026 08:05:57 +0100 Subject: [PATCH 18/23] Introduce 'prover' feature --- Cargo.toml | 6 +++++- src/demo.rs | 13 +++++-------- src/lib.rs | 6 +++--- src/ring/bandersnatch.rs | 8 ++++---- src/ring/mod.rs | 16 ++++++++-------- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5c3c95d..4fcf5e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ path = "utils/generate_test_keys.rs" [features] default = ["std"] std = [ + "prover", "bounded-collections/std", "parity-scale-codec/std", "scale-info/std", @@ -45,9 +46,12 @@ std = [ # Disable this feature to reduce library size in production environments # that only need to validate proofs (not build new ring commitments). builder-params = [] -schnorrkel = ["dep:schnorrkel"] +# Enables ring proof generation (prover) functionality. +prover = [] # Prover for no-std environments with deterministic ring-proof. # Not for production, may be useful for testing. no-std-prover = [ + "prover", "ark-vrf/test-vectors", ] +schnorrkel = ["dep:schnorrkel"] diff --git a/src/demo.rs b/src/demo.rs index 014c388..f59886f 100644 --- a/src/demo.rs +++ b/src/demo.rs @@ -58,7 +58,7 @@ impl GenerateVerifiable for Trivial { secret.clone() } - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn open( _capacity: (), member: &Self::Member, @@ -71,7 +71,7 @@ impl GenerateVerifiable for Trivial { Ok((member.clone(), set)) } - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn create( (member, _): Self::Commitment, secret: &Self::Secret, @@ -168,7 +168,7 @@ impl GenerateVerifiable for Simple { pair.public.to_bytes() } - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn open( _capacity: (), member: &Self::Member, @@ -181,7 +181,7 @@ impl GenerateVerifiable for Simple { Ok((member.clone(), set)) } - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn create( (member, _): Self::Commitment, secret: &Self::Secret, @@ -248,10 +248,7 @@ impl GenerateVerifiable for Simple { mod tests { use super::*; - #[cfg(all( - feature = "schnorrkel", - any(feature = "std", feature = "no-std-prover") - ))] + #[cfg(all(feature = "schnorrkel", feature = "prover"))] #[test] fn simple_works() { let alice_sec = ::new_secret([0u8; 32]); diff --git a/src/lib.rs b/src/lib.rs index 1f22ddd..c99617b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,7 +122,7 @@ pub trait GenerateVerifiable { /// /// **WARNING**: This function may panic if called from on-chain or an environment not /// implementing the functionality. - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn open( capacity: Self::Capacity, member: &Self::Member, @@ -144,7 +144,7 @@ pub trait GenerateVerifiable { /// /// **WARNING**: This function may panic if called from on-chain or an environment not /// implementing the functionality. - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn create( commitment: Self::Commitment, secret: &Self::Secret, @@ -208,7 +208,7 @@ pub struct Receipt { } impl Receipt { - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] pub fn create<'a>( capacity: Gen::Capacity, secret: &Gen::Secret, diff --git a/src/ring/bandersnatch.rs b/src/ring/bandersnatch.rs index b8ec074..5398388 100644 --- a/src/ring/bandersnatch.rs +++ b/src/ring/bandersnatch.rs @@ -1,16 +1,16 @@ use crate::ring::{Bls12_381RingData, RingSuiteExt, RingVrfVerifiable}; use ark_vrf::suites::bandersnatch::{BandersnatchSha512Ell2, RingProofParams}; -#[cfg(any(feature = "std", feature = "no-std-prover"))] +#[cfg(feature = "prover")] use crate::ring::{make_ring_prover_params, RingDomainSize, RingProofParamsCache}; /// Bandersnatch ring VRF Verifiable (BandersnatchSha512Ell2 suite). pub type BandersnatchVrfVerifiable = RingVrfVerifiable; -#[cfg(any(feature = "std", feature = "no-std-prover"))] +#[cfg(feature = "prover")] pub struct BandersnatchParamsCache; -#[cfg(any(feature = "std", feature = "no-std-prover"))] +#[cfg(feature = "prover")] impl RingProofParamsCache for BandersnatchParamsCache { type Handle = &'static ark_vrf::ring::RingProofParams; @@ -41,7 +41,7 @@ impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { type RingProofBytes = [u8; Self::RING_PROOF_SIZE]; type SignatureBytes = [u8; Self::SIGNATURE_SIZE]; - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] type ParamsCache = BandersnatchParamsCache; } diff --git a/src/ring/mod.rs b/src/ring/mod.rs index eb30826..fd3ba90 100644 --- a/src/ring/mod.rs +++ b/src/ring/mod.rs @@ -79,7 +79,7 @@ impl Capacity for RingSize { /// For example, all suites using BLS12-381 (like Bandersnatch) use `Bls12_381RingData`. pub trait RingCurveData { /// Raw SRS data (powers of tau). - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn srs_raw() -> &'static [u8]; /// Ring builder params for a given domain size. @@ -94,7 +94,7 @@ pub trait RingCurveData { pub struct Bls12_381RingData; impl RingCurveData for Bls12_381RingData { - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn srs_raw() -> &'static [u8] { include_bytes!("data/bls12-381/srs-uncompressed.bin") } @@ -129,7 +129,7 @@ const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: us } /// Construct ring prover params from the suite's SRS data. -#[cfg(any(feature = "std", feature = "no-std-prover"))] +#[cfg(feature = "prover")] pub fn make_ring_prover_params( domain_size: RingDomainSize, ) -> ark_vrf::ring::RingProofParams { @@ -153,7 +153,7 @@ pub fn ring_verifier_builder_params( /// Trait for caching ring proof params. /// /// The `Handle` associated type allows different caching strategies. -#[cfg(any(feature = "std", feature = "no-std-prover"))] +#[cfg(feature = "prover")] pub trait RingProofParamsCache { /// Handle type returned by the cache. Must deref to `RingProofParams`. type Handle: Deref>; @@ -163,7 +163,7 @@ pub trait RingProofParamsCache { } /// No-caching implementation: always constructs new params. -#[cfg(any(feature = "std", feature = "no-std-prover"))] +#[cfg(feature = "prover")] impl RingProofParamsCache for () { type Handle = alloc::boxed::Box>; @@ -213,7 +213,7 @@ pub trait RingSuiteExt: RingSuite + 'static { /// /// Use `()` for no caching (always construct), or a type generated by /// [`impl_ring_params_cache!`] for static caching. - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] type ParamsCache: RingProofParamsCache; } @@ -463,7 +463,7 @@ impl GenerateVerifiable for RingVrfVerifiable { .is_ok() } - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn open( capacity: Self::Capacity, member: &Self::Member, @@ -479,7 +479,7 @@ impl GenerateVerifiable for RingVrfVerifiable { Ok((capacity, member_idx, prover_key.into())) } - #[cfg(any(feature = "std", feature = "no-std-prover"))] + #[cfg(feature = "prover")] fn create( commitment: Self::Commitment, secret: &Self::Secret, From 640519c00c3a9d3b5c9819f3540fa7052f74ec2b Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 19 Feb 2026 10:17:54 +0100 Subject: [PATCH 19/23] NullCache alias --- src/ring/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ring/mod.rs b/src/ring/mod.rs index fd3ba90..245743c 100644 --- a/src/ring/mod.rs +++ b/src/ring/mod.rs @@ -162,9 +162,12 @@ pub trait RingProofParamsCache { fn get(domain_size: RingDomainSize) -> Self::Handle; } -/// No-caching implementation: always constructs new params. +/// Null cache: always constructs new params without caching. #[cfg(feature = "prover")] -impl RingProofParamsCache for () { +pub type NullCache = (); + +#[cfg(feature = "prover")] +impl RingProofParamsCache for NullCache { type Handle = alloc::boxed::Box>; fn get(domain_size: RingDomainSize) -> Self::Handle { @@ -211,7 +214,7 @@ pub trait RingSuiteExt: RingSuite + 'static { /// Cache strategy for ring proof params. /// - /// Use `()` for no caching (always construct), or a type generated by + /// Use [`NullCache`] for no caching (always construct), or a type generated by /// [`impl_ring_params_cache!`] for static caching. #[cfg(feature = "prover")] type ParamsCache: RingProofParamsCache; From 76ae50a88f1a9cfc1ecdfbe17e42964e73f34601 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 19 Feb 2026 10:20:23 +0100 Subject: [PATCH 20/23] DecodeWithMemTracking as additional bound for EncodedTypesBounds --- src/ring/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ring/mod.rs b/src/ring/mod.rs index 245743c..a19fe36 100644 --- a/src/ring/mod.rs +++ b/src/ring/mod.rs @@ -176,7 +176,15 @@ impl RingProofParamsCache for NullCache { } pub trait EncodedTypesBounds: - Clone + Eq + FullCodec + core::fmt::Debug + TypeInfo + MaxEncodedLen + AsRef<[u8]> + AsMut<[u8]> + Clone + + Eq + + FullCodec + + DecodeWithMemTracking + + core::fmt::Debug + + TypeInfo + + MaxEncodedLen + + AsRef<[u8]> + + AsMut<[u8]> { const ZERO: Self; } From 827abf7e8876ef33adbfd8d1e44ea0eafcd4b1d0 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 19 Feb 2026 10:32:56 +0100 Subject: [PATCH 21/23] Don't panic if hash length is <32 --- src/ring/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ring/mod.rs b/src/ring/mod.rs index a19fe36..13ef998 100644 --- a/src/ring/mod.rs +++ b/src/ring/mod.rs @@ -324,7 +324,11 @@ struct RingVrfSignature { #[inline(always)] fn make_alias(output: &ark_vrf::Output) -> Alias { - Alias::try_from(&output.hash()[..32]).expect("Suite hash should be at least 32 bytes") + output + .hash() + .get(..32) + .and_then(|raw| raw.try_into().ok()) + .expect("Suite hash should be at least 32 bytes") } /// Generic ring VRF implementation parameterized over the ring suite. From c206c6e3839c8cd166cb947cc943ce85da6fd11d Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 19 Feb 2026 10:42:43 +0100 Subject: [PATCH 22/23] Renaming --- src/ring/bandersnatch.rs | 4 ++-- src/ring/mod.rs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ring/bandersnatch.rs b/src/ring/bandersnatch.rs index 5398388..f00f979 100644 --- a/src/ring/bandersnatch.rs +++ b/src/ring/bandersnatch.rs @@ -1,4 +1,4 @@ -use crate::ring::{Bls12_381RingData, RingSuiteExt, RingVrfVerifiable}; +use crate::ring::{Bls12_381Params, RingSuiteExt, RingVrfVerifiable}; use ark_vrf::suites::bandersnatch::{BandersnatchSha512Ell2, RingProofParams}; #[cfg(feature = "prover")] @@ -35,7 +35,7 @@ impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { const RING_PROOF_SIZE: usize = 788; const SIGNATURE_SIZE: usize = 96; - type CurveData = Bls12_381RingData; + type CurveParams = Bls12_381Params; type PublicKeyBytes = [u8; Self::PUBLIC_KEY_SIZE]; type RingProofBytes = [u8; Self::RING_PROOF_SIZE]; diff --git a/src/ring/mod.rs b/src/ring/mod.rs index 13ef998..1273ce7 100644 --- a/src/ring/mod.rs +++ b/src/ring/mod.rs @@ -76,8 +76,8 @@ impl Capacity for RingSize { /// Trait for providing pairing-curve-specific ring data. /// /// All RingSuites that use the same pairing curve can share the same data provider. -/// For example, all suites using BLS12-381 (like Bandersnatch) use `Bls12_381RingData`. -pub trait RingCurveData { +/// For example, all suites using BLS12-381 (like Bandersnatch) use `Bls12_381Params`. +pub trait RingCurveParams { /// Raw SRS data (powers of tau). #[cfg(feature = "prover")] fn srs_raw() -> &'static [u8]; @@ -91,9 +91,9 @@ pub trait RingCurveData { } /// Ring data for suites using BLS12-381 pairing (e.g., Bandersnatch curves). -pub struct Bls12_381RingData; +pub struct Bls12_381Params; -impl RingCurveData for Bls12_381RingData { +impl RingCurveParams for Bls12_381Params { #[cfg(feature = "prover")] fn srs_raw() -> &'static [u8] { include_bytes!("data/bls12-381/srs-uncompressed.bin") @@ -133,7 +133,7 @@ const fn max_ring_size_from_pcs_domain_size(pcs_domain_size: us pub fn make_ring_prover_params( domain_size: RingDomainSize, ) -> ark_vrf::ring::RingProofParams { - let data = S::CurveData::srs_raw(); + let data = S::CurveParams::srs_raw(); let pcs_params = ark_vrf::ring::PcsParams::::deserialize_uncompressed_unchecked(data).unwrap(); let ring_size = max_ring_size_from_pcs_domain_size::(domain_size.pcs_domain_size()); @@ -146,7 +146,7 @@ pub fn make_ring_prover_params( pub fn ring_verifier_builder_params( domain_size: RingDomainSize, ) -> ark_vrf::ring::RingBuilderPcsParams { - let data = S::CurveData::ring_builder_params(domain_size); + let data = S::CurveParams::ring_builder_params(domain_size); ark_vrf::ring::RingBuilderPcsParams::::deserialize_uncompressed_unchecked(data).unwrap() } @@ -218,7 +218,7 @@ pub trait RingSuiteExt: RingSuite + 'static { type SignatureBytes: EncodedTypesBounds; /// The curve static data provider for this suite. - type CurveData: RingCurveData; + type CurveParams: RingCurveParams; /// Cache strategy for ring proof params. /// @@ -333,7 +333,7 @@ fn make_alias(output: &ark_vrf::Output) -> Alias { /// Generic ring VRF implementation parameterized over the ring suite. /// -/// The curve data provider is obtained from `S::CurveData`. +/// The curve params provider is obtained from `S::CurveParams`. pub struct RingVrfVerifiable(PhantomData); const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableVrfInput"; @@ -371,7 +371,7 @@ impl GenerateVerifiable for RingVrfVerifiable { fn start_members(capacity: Self::Capacity) -> Self::Intermediate { // TODO: Optimize by caching the deserialized value; must be compatible with the WASM runtime environment. - let data = S::CurveData::empty_ring_commitment(capacity.dom_size); + let data = S::CurveParams::empty_ring_commitment(capacity.dom_size); MembersSet::deserialize_uncompressed_unchecked(data).unwrap() } From 4037a8ea2a82a5273e6660ebc41d42bb641772e9 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 19 Feb 2026 11:03:07 +0100 Subject: [PATCH 23/23] Move VRF input domain separator to RingSuiteExt trait to ensure backward compat --- src/ring/bandersnatch.rs | 2 ++ src/ring/mod.rs | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ring/bandersnatch.rs b/src/ring/bandersnatch.rs index f00f979..48ebe3e 100644 --- a/src/ring/bandersnatch.rs +++ b/src/ring/bandersnatch.rs @@ -28,6 +28,8 @@ impl RingProofParamsCache for BandersnatchParamsCache { } impl RingSuiteExt for ark_vrf::suites::bandersnatch::BandersnatchSha512Ell2 { + const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableBandersnatchVrfInput"; + const PUBLIC_KEY_SIZE: usize = 32; const MEMBERS_SET_SIZE: usize = 432; const MEMBERS_COMMITMENT_SIZE: usize = 384; diff --git a/src/ring/mod.rs b/src/ring/mod.rs index 1273ce7..04f9811 100644 --- a/src/ring/mod.rs +++ b/src/ring/mod.rs @@ -197,6 +197,12 @@ impl EncodedTypesBounds for [u8; N] { /// This trait defines the concrete array types used for serialized forms of /// public keys, proofs, and signatures. Each suite specifies its own sizes. pub trait RingSuiteExt: RingSuite + 'static { + /// VRF input domain separator. + /// + /// Used to construct the VRF input from context/message data. + /// Each suite should use a unique domain separator to avoid cross-suite collisions. + const VRF_INPUT_DOMAIN: &[u8]; + /// Encoded size of a public key. const PUBLIC_KEY_SIZE: usize; /// Encoded size of MembersSet (Intermediate). @@ -221,9 +227,6 @@ pub trait RingSuiteExt: RingSuite + 'static { type CurveParams: RingCurveParams; /// Cache strategy for ring proof params. - /// - /// Use [`NullCache`] for no caching (always construct), or a type generated by - /// [`impl_ring_params_cache!`] for static caching. #[cfg(feature = "prover")] type ParamsCache: RingProofParamsCache; } @@ -336,8 +339,6 @@ fn make_alias(output: &ark_vrf::Output) -> Alias { /// The curve params provider is obtained from `S::CurveParams`. pub struct RingVrfVerifiable(PhantomData); -const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableVrfInput"; - impl RingVrfVerifiable { fn to_public_key(value: &S::PublicKeyBytes) -> Result, ()> { let pt = @@ -422,7 +423,7 @@ impl GenerateVerifiable for RingVrfVerifiable { capacity.size(), ); - let input_msg = [VRF_INPUT_DOMAIN, context].concat(); + let input_msg = [S::VRF_INPUT_DOMAIN, context].concat(); let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let signature = RingVrfSignature::::deserialize_compressed_unchecked(proof.as_ref()) @@ -442,7 +443,7 @@ impl GenerateVerifiable for RingVrfVerifiable { fn sign(secret: &Self::Secret, message: &[u8]) -> Result { use ark_vrf::ietf::Prover; - let input_msg = [VRF_INPUT_DOMAIN, message].concat(); + let input_msg = [S::VRF_INPUT_DOMAIN, message].concat(); let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let output = secret.output(input); @@ -467,7 +468,7 @@ impl GenerateVerifiable for RingVrfVerifiable { else { return false; }; - let input_msg = [VRF_INPUT_DOMAIN, message].concat(); + let input_msg = [S::VRF_INPUT_DOMAIN, message].concat(); let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let Ok(member) = Self::to_public_key(member) else { return false; @@ -510,7 +511,7 @@ impl GenerateVerifiable for RingVrfVerifiable { let ring_prover = params.prover(prover_key.0, prover_idx as usize); - let input_msg = [VRF_INPUT_DOMAIN, context].concat(); + let input_msg = [S::VRF_INPUT_DOMAIN, context].concat(); let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let preout = secret.output(input); let alias = make_alias(&preout); @@ -531,7 +532,7 @@ impl GenerateVerifiable for RingVrfVerifiable { } fn alias_in_context(secret: &Self::Secret, context: &[u8]) -> Result { - let input_msg = [VRF_INPUT_DOMAIN, context].concat(); + let input_msg = [S::VRF_INPUT_DOMAIN, context].concat(); let input = ark_vrf::Input::::new(&input_msg[..]).expect("H2C can't fail here"); let output = secret.output(input); let alias = make_alias(&output);