diff --git a/Cargo.toml b/Cargo.toml index 7f24781..4fcf5e1 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" @@ -19,8 +17,8 @@ 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"] } -spin = { version = "0.9", default-features = false, features = ["once"], optional = true } +ark-vrf = { version = "0.1.1", default-features = false, features = ["bandersnatch", "ring"] } +spin = { version = "0.9", default-features = false, features = ["once"] } [dev-dependencies] rand = { version = "0.8", features = ["getrandom"] } @@ -34,6 +32,7 @@ path = "utils/generate_test_keys.rs" [features] default = ["std"] std = [ + "prover", "bounded-collections/std", "parity-scale-codec/std", "scale-info/std", @@ -47,10 +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 = [ - "spin", + "prover", "ark-vrf/test-vectors", ] +schnorrkel = ["dep:schnorrkel"] diff --git a/src/demo_impls.rs b/src/demo.rs similarity index 96% rename from src/demo_impls.rs rename to src/demo.rs index 014c388..f59886f 100644 --- a/src/demo_impls.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 ba96653..c99617b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,8 @@ use core::{fmt::Debug, ops::Range}; use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, FullCodec, MaxEncodedLen}; use scale_info::*; -pub mod demo_impls; -pub mod ring_vrf_impl; +pub mod demo; +pub mod ring; /// Trait for capacity types used in ring operations. /// @@ -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 new file mode 100644 index 0000000..48ebe3e --- /dev/null +++ b/src/ring/bandersnatch.rs @@ -0,0 +1,634 @@ +use crate::ring::{Bls12_381Params, RingSuiteExt, RingVrfVerifiable}; +use ark_vrf::suites::bandersnatch::{BandersnatchSha512Ell2, RingProofParams}; + +#[cfg(feature = "prover")] +use crate::ring::{make_ring_prover_params, RingDomainSize, RingProofParamsCache}; + +/// Bandersnatch ring VRF Verifiable (BandersnatchSha512Ell2 suite). +pub type BandersnatchVrfVerifiable = RingVrfVerifiable; + +#[cfg(feature = "prover")] +pub struct BandersnatchParamsCache; + +#[cfg(feature = "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 VRF_INPUT_DOMAIN: &[u8] = b"VerifiableBandersnatchVrfInput"; + + 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 CurveParams = Bls12_381Params; + + type PublicKeyBytes = [u8; Self::PUBLIC_KEY_SIZE]; + type RingProofBytes = [u8; Self::RING_PROOF_SIZE]; + type SignatureBytes = [u8; Self::SIGNATURE_SIZE]; + + #[cfg(feature = "prover")] + type ParamsCache = BandersnatchParamsCache; +} + +#[cfg(test)] +mod tests { + use ark_scale::MaxEncodedLen; + use ark_serialize::CanonicalSerialize; + use ark_vrf::ring::SrsLookup; + + use super::*; + use crate::{ring::RingSize, Capacity, 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"; + 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)); + } + + #[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() { + 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, 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; + + use tests::{ + bandersnatch_ring_prover_params, start_members_from_params, MembersCommitment, MembersSet, + PublicKey, + }; + + pub type RingSize = crate::ring::RingSize; + + /// 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 + } + } + }; + } + + #[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/bls12-381/zcash-srs-2-16-uncompressed.bin" + ); + const SRS_COMPRESSED_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/ring/data/bls12-381/srs-compressed.bin" + ); + const SRS_UNCOMPRESSED_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/ring/data/bls12-381/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/bls12-381/ring-builder-domain{}.bin", + env!("CARGO_MANIFEST_DIR"), + domain_size.as_power() + ); + let params_file = format!( + "{}/src/ring/data/bls12-381/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| crate::ring::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| crate::ring::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| crate::ring::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| crate::ring::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-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 new file mode 100644 index 0000000..04f9811 --- /dev/null +++ b/src/ring/mod.rs @@ -0,0 +1,545 @@ +use alloc::vec; +use core::{marker::PhantomData, ops::Deref, ops::Range}; + +pub use ark_vrf; + +use ark_scale::ArkScale; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_vrf::ring::{RingSuite, Verifier}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; + +use super::*; + +pub mod bandersnatch; + +/// Domain sizes for the PCS (Polynomial Commitment Scheme). +/// +/// 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 + Domain11, + /// Domain size 2^12 + Domain12, + /// Domain size 2^16 + Domain16, +} + +impl RingDomainSize { + /// Returns the domain size as a power of 2. + pub const fn as_power(self) -> u32 { + match self { + RingDomainSize::Domain11 => 11, + RingDomainSize::Domain12 => 12, + RingDomainSize::Domain16 => 16, + } + } + + /// Returns the actual PCS domain size (2^power). + pub const fn pcs_domain_size(self) -> usize { + 1 << self.as_power() + } +} + +/// 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()) + } +} + +/// 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_381Params`. +pub trait RingCurveParams { + /// Raw SRS data (powers of tau). + #[cfg(feature = "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]; +} + +/// Ring data for suites using BLS12-381 pairing (e.g., Bandersnatch curves). +pub struct Bls12_381Params; + +impl RingCurveParams for Bls12_381Params { + #[cfg(feature = "prover")] + fn srs_raw() -> &'static [u8] { + 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/bls12-381/ring-builder-params-domain11.bin") + } + RingDomainSize::Domain12 => { + include_bytes!("data/bls12-381/ring-builder-params-domain12.bin") + } + RingDomainSize::Domain16 => { + 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/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"), + } + } +} + +/// 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(feature = "prover")] +pub fn make_ring_prover_params( + domain_size: RingDomainSize, +) -> ark_vrf::ring::RingProofParams { + 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()); + 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::CurveParams::ring_builder_params(domain_size); + ark_vrf::ring::RingBuilderPcsParams::::deserialize_uncompressed_unchecked(data).unwrap() +} + +/// Trait for caching ring proof params. +/// +/// The `Handle` associated type allows different caching strategies. +#[cfg(feature = "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; +} + +/// Null cache: always constructs new params without caching. +#[cfg(feature = "prover")] +pub type NullCache = (); + +#[cfg(feature = "prover")] +impl RingProofParamsCache for NullCache { + 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 + + DecodeWithMemTracking + + 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 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). + 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; + /// 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; + /// 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 CurveParams: RingCurveParams; + + /// Cache strategy for ring proof params. + #[cfg(feature = "prover")] + type ParamsCache: RingProofParamsCache; +} + +macro_rules! impl_common_traits { + // 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)) + })) + } + } + + 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 {} + }; +} + +#[derive(CanonicalDeserialize, CanonicalSerialize)] +#[derive_where::derive_where(Clone)] +pub struct MembersSet(pub(crate) ark_vrf::ring::RingVerifierKeyBuilder); + +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 { + write!(f, "MembersSet") + } +} + +#[derive(CanonicalDeserialize, CanonicalSerialize)] +#[derive_where::derive_where(Clone)] +pub struct MembersCommitment(pub(crate) ark_vrf::ring::RingVerifierKey); + +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 { + write!(f, "MembersCommitment") + } +} + +#[derive(CanonicalSerialize, CanonicalDeserialize)] +#[derive_where::derive_where(Clone, Debug)] +pub struct PublicKey(pub(crate) ark_vrf::AffinePoint); + +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_common_traits!(StaticChunk, S::STATIC_CHUNK_SIZE); + +#[derive(CanonicalSerialize, CanonicalDeserialize)] +struct IetfVrfSignature { + output: ark_vrf::Output, + proof: ark_vrf::ietf::Proof, +} + +#[derive(CanonicalSerialize, CanonicalDeserialize)] +struct RingVrfSignature { + output: ark_vrf::Output, + proof: ark_vrf::ring::Proof, +} + +#[inline(always)] +fn make_alias(output: &ark_vrf::Output) -> Alias { + 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. +/// +/// The curve params provider is obtained from `S::CurveParams`. +pub struct RingVrfVerifiable(PhantomData); + +impl RingVrfVerifiable { + 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::PublicKeyBytes { + let mut bytes = S::PublicKeyBytes::ZERO; + value.using_encoded(|encoded| { + bytes.as_mut().copy_from_slice(encoded); + }); + bytes + } +} + +impl GenerateVerifiable for RingVrfVerifiable { + type Members = MembersCommitment; + type Intermediate = MembersSet; + type Member = S::PublicKeyBytes; + type Secret = ark_vrf::Secret; + type Commitment = ( + Self::Capacity, + u32, + ArkScale>, + ); + type Proof = S::RingProofBytes; + type Signature = S::SignatureBytes; + type StaticChunk = StaticChunk; + type Capacity = RingSize; + + 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::CurveParams::empty_ring_commitment(capacity.dom_size); + MembersSet::deserialize_uncompressed_unchecked(data).unwrap() + } + + fn push_members( + intermediate: &mut Self::Intermediate, + members: impl Iterator, + lookup: impl Fn(Range) -> Result, ()>, + ) -> Result<(), ()> { + let mut keys = vec![]; + for member in members { + keys.push(Self::to_public_key(&member)?.0); + } + let loader = |range: Range| { + let items = lookup(range) + .ok()? + .into_iter() + .map(|c| c.0) + .collect::>(); + Some(items) + }; + intermediate.0.append(&keys[..], loader).map_err(|_| ()) + } + + fn finish_members(intermediate: Self::Intermediate) -> Self::Members { + let verifier_key = intermediate.0.finalize(); + MembersCommitment(verifier_key) + } + + fn new_secret(entropy: Entropy) -> Self::Secret { + Self::Secret::from_seed(&entropy) + } + + fn member_from_secret(secret: &Self::Secret) -> Self::Member { + Self::to_encoded_public_key(&PublicKey(secret.public().0)) + } + + fn validate( + capacity: Self::Capacity, + proof: &Self::Proof, + members: &Self::Members, + context: &[u8], + message: &[u8], + ) -> 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 = ark_vrf::ring::RingProofParams::::verifier_no_context( + members.0.clone(), + capacity.size(), + ); + + 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()) + .map_err(|_| ())?; + + ark_vrf::Public::::verify( + input, + signature.output, + message, + &signature.proof, + &ring_verifier, + ) + .map_err(|_| ())?; + + Ok(make_alias(&signature.output)) + } + + fn sign(secret: &Self::Secret, message: &[u8]) -> Result { + use ark_vrf::ietf::Prover; + 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); + + let proof = secret.prove(input, output, b""); + let signature = IetfVrfSignature:: { output, proof }; + + let mut raw = S::SignatureBytes::ZERO; + signature + .serialize_compressed(raw.as_mut()) + .map_err(|_| ())?; + Ok(raw) + } + + fn verify_signature( + signature: &Self::Signature, + message: &[u8], + member: &Self::Member, + ) -> bool { + use ark_vrf::ietf::Verifier; + let Ok(signature) = + IetfVrfSignature::::deserialize_compressed_unchecked(signature.as_ref()) + else { + return false; + }; + 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; + }; + let public = ark_vrf::Public::::from(member.0); + public + .verify(input, signature.output, b"", &signature.proof) + .is_ok() + } + + #[cfg(feature = "prover")] + fn open( + capacity: Self::Capacity, + member: &Self::Member, + members: impl Iterator, + ) -> Result { + let pks = members + .map(|m| Self::to_public_key(&m).map(|pk| pk.0)) + .collect::, _>>()?; + 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::ParamsCache::get(capacity.dom_size).prover_key(&pks[..]); + Ok((capacity, member_idx, prover_key.into())) + } + + #[cfg(feature = "prover")] + fn create( + commitment: Self::Commitment, + secret: &Self::Secret, + context: &[u8], + message: &[u8], + ) -> Result<(Self::Proof, Alias), ()> { + use ark_vrf::ring::Prover; + let (capacity, prover_idx, prover_key) = commitment; + let params = S::ParamsCache::get(capacity.dom_size); + if prover_idx >= params.max_ring_size() as u32 { + return Err(()); + } + + let ring_prover = params.prover(prover_key.0, prover_idx as usize); + + 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); + + let proof = secret.prove(input, preout, message, &ring_prover); + + let signature = RingVrfSignature:: { + output: preout, + proof, + }; + + let mut buf = S::RingProofBytes::ZERO; + signature + .serialize_compressed(buf.as_mut()) + .map_err(|_| ())?; + + Ok((buf, alias)) + } + + fn alias_in_context(secret: &Self::Secret, context: &[u8]) -> Result { + 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); + Ok(alias) + } + + fn is_member_valid(member: &Self::Member) -> bool { + Self::to_public_key(member).is_ok() + } +} diff --git a/src/ring_vrf_impl.rs b/src/ring_vrf_impl.rs deleted file mode 100644 index a23d228..0000000 --- a/src/ring_vrf_impl.rs +++ /dev/null @@ -1,917 +0,0 @@ -use alloc::vec; -use core::ops::Range; - -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::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::( - pcs_domain_size, - ) -} - -/// Concrete 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, -)] -pub enum RingDomainSize { - /// Domain size 2^11, max ring size 255 - Domain11, - /// Domain size 2^12, max ring size 767 - Domain12, - /// Domain size 2^16, max ring size 16127 - Domain16, -} - -impl RingDomainSize { - /// Returns the domain size as a power of 2. - pub const fn as_power(self) -> u32 { - match self { - RingDomainSize::Domain11 => 11, - RingDomainSize::Domain12 => 12, - RingDomainSize::Domain16 => 16, - } - } - - /// Returns the actual PCS domain size (2^power). - pub const fn pcs_domain_size(self) -> usize { - 1 << self.as_power() - } - - /// Returns the maximum ring size that can be handled for both sign/verify for a domain size. - pub const fn max_ring_size(&self) -> usize { - max_ring_size_from_pcs_domain_size(self.pcs_domain_size()) - } - - /// All available domain sizes. - pub const ALL: [RingDomainSize; 3] = [ - RingDomainSize::Domain11, - RingDomainSize::Domain12, - RingDomainSize::Domain16, - ]; -} - -impl Capacity for RingDomainSize { - fn size(&self) -> usize { - self.max_ring_size() - } -} - -/// 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"); -} -#[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"); -} -use ring_commitment_data::*; - -const VRF_INPUT_DOMAIN: &[u8] = b"VerifiableBandersnatchVrfInput"; - -/// 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_scale { - ($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() - } - } - }; -} - -#[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(VERIFIABLE_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(VERIFIABLE_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 = 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 inner = - >>::deserialize_uncompressed_unchecked(data).unwrap(); - ark_vrf::ring::RingBuilderPcsParams::(inner) -} - -#[derive(Clone, CanonicalDeserialize, CanonicalSerialize)] -pub struct MembersSet(bandersnatch::RingVerifierKeyBuilder); - -impl_scale!(MembersSet, 432); - -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 { - fn eq(&self, other: &Self) -> bool { - self.encode() == other.encode() - } -} - -impl core::cmp::Eq for MembersSet {} - -#[derive(Clone, CanonicalDeserialize, CanonicalSerialize)] -pub struct MembersCommitment(bandersnatch::RingVerifierKey); - -impl_scale!(MembersCommitment, 384); - -impl core::fmt::Debug for MembersCommitment { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "MemberCommitment") - } -} - -impl core::cmp::PartialEq for MembersCommitment { - fn eq(&self, other: &Self) -> bool { - self.encode() == other.encode() - } -} -impl core::cmp::Eq 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(Clone, Eq, PartialEq, Debug, CanonicalSerialize, CanonicalDeserialize)] -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); - -impl DecodeWithMemTracking for StaticChunk {} - -#[derive(CanonicalSerialize, CanonicalDeserialize)] -struct IetfVrfSignature { - output: bandersnatch::Output, - proof: bandersnatch::IetfProof, -} -const PLAIN_VRF_SIGNATURE_SIZE: usize = 96; - -#[derive(CanonicalSerialize, CanonicalDeserialize)] -struct RingVrfSignature { - output: bandersnatch::Output, - proof: bandersnatch::RingProof, -} -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") -} - -pub struct BandersnatchVrfVerifiable; - -impl BandersnatchVrfVerifiable { - fn to_public_key(value: &EncodedPublicKey) -> Result { - let pt = bandersnatch::AffinePoint::deserialize_compressed(&value.0[..]).map_err(|_| ())?; - Ok(PublicKey(pt.into())) - } - - fn to_encoded_public_key(value: &PublicKey) -> EncodedPublicKey { - let mut bytes = [0u8; PUBLIC_KEY_SIZE]; - value.using_encoded(|encoded| { - bytes.copy_from_slice(encoded); - }); - EncodedPublicKey(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 = [u8; RING_VRF_SIGNATURE_SIZE]; - type Signature = [u8; PLAIN_VRF_SIGNATURE_SIZE]; - 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 = 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, - }; - MembersSet::deserialize_uncompressed_unchecked(data).unwrap() - } - - fn push_members( - intermediate: &mut Self::Intermediate, - members: impl Iterator, - lookup: impl Fn(Range) -> Result, ()>, - ) -> Result<(), ()> { - let mut keys = vec![]; - for member in members { - keys.push(Self::to_public_key(&member)?.0); - } - let loader = |range: Range| { - let items = lookup(range) - .ok()? - .into_iter() - .map(|c| c.0) - .collect::>(); - Some(items) - }; - intermediate.0.append(&keys[..], loader).map_err(|_| ()) - } - - fn finish_members(intermediate: Self::Intermediate) -> Self::Members { - let verifier_key = intermediate.0.finalize(); - MembersCommitment(verifier_key) - } - - fn new_secret(entropy: Entropy) -> Self::Secret { - Self::Secret::from_seed(&entropy) - } - - fn member_from_secret(secret: &Self::Secret) -> Self::Member { - Self::to_encoded_public_key(&PublicKey(secret.public().0)) - } - - fn validate( - capacity: RingDomainSize, - proof: &Self::Proof, - members: &Self::Members, - context: &[u8], - message: &[u8], - ) -> 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 input_msg = [VRF_INPUT_DOMAIN, context].concat(); - let input = bandersnatch::Input::new(&input_msg[..]).expect("H2C can't fail here"); - - let signature = - RingVrfSignature::deserialize_compressed(proof.as_slice()).map_err(|_| ())?; - - bandersnatch::Public::verify( - input, - signature.output, - message, - &signature.proof, - &ring_verifier, - ) - .map_err(|_| ())?; - - Ok(make_alias(&signature.output)) - } - - 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 output = secret.output(input); - - let proof = secret.prove(input, output, b""); - let signature = IetfVrfSignature { output, proof }; - - let mut raw = [0u8; PLAIN_VRF_SIGNATURE_SIZE]; - signature - .serialize_compressed(raw.as_mut_slice()) - .map_err(|_| ())?; - Ok(raw) - } - - fn verify_signature( - signature: &Self::Signature, - message: &[u8], - member: &Self::Member, - ) -> bool { - use ark_vrf::ietf::Verifier; - let Ok(signature) = IetfVrfSignature::deserialize_compressed(signature.as_slice()) 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 Ok(member) = Self::to_public_key(member) else { - return false; - }; - let public = bandersnatch::Public::from(member.0); - public - .verify(input, signature.output, b"", &signature.proof) - .is_ok() - } - - #[cfg(any(feature = "std", feature = "no-std-prover"))] - fn open( - capacity: RingDomainSize, - member: &Self::Member, - members: impl Iterator, - ) -> Result { - let pks = members - .map(|m| Self::to_public_key(&m).map(|pk| pk.0)) - .collect::, _>>()?; - 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[..]); - Ok((capacity, member_idx, prover_key.into())) - } - - #[cfg(any(feature = "std", feature = "no-std-prover"))] - fn create( - commitment: Self::Commitment, - secret: &Self::Secret, - context: &[u8], - message: &[u8], - ) -> Result<(Self::Proof, Alias), ()> { - use ark_vrf::ring::Prover; - let (domain_size, prover_idx, prover_key) = commitment; - let params = ring_prover_params(domain_size); - if prover_idx >= params.max_ring_size() as u32 { - return Err(()); - } - - 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 preout = secret.output(input); - let alias = make_alias(&preout); - - let proof = secret.prove(input, preout, message, &ring_prover); - - let signature = RingVrfSignature { - output: preout, - proof, - }; - - let mut buf = [0u8; RING_VRF_SIGNATURE_SIZE]; - signature - .serialize_compressed(buf.as_mut_slice()) - .map_err(|_| ())?; - - Ok((buf, alias)) - } - - 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 output = secret.output(input); - let alias = make_alias(&output); - Ok(alias) - } - - fn is_member_valid(member: &Self::Member) -> bool { - Self::to_public_key(member).is_ok() - } -} - -#[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 = EncodedPublicKey([0; 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 { - use super::*; - use ark_vrf::{ring::SrsLookup, suites::bandersnatch::BandersnatchSha512Ell2}; - - 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 start_members_from_params( - domain_size: RingDomainSize, - ) -> (MembersSet, RingBuilderPcsParams) { - let (builder, builder_pcs_params) = 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 = 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; - - for domain_size in RingDomainSize::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 builder = BandersnatchVrfVerifiable::start_members(domain_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 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); - 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 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(domain_size); - 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 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(domain_size); - // 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(domain_size); - // Fourth set is a single addition followed by a group addition. - let mut inter4 = BandersnatchVrfVerifiable::start_members(domain_size); - - // 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 context = b"Context"; - let message = b"FooBar"; - - let start = Instant::now(); - let _ = 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(domain_size, &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(domain_size); - 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(domain_size, &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 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(); - 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(domain_size); - 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(domain_size); - 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/utils/generate_test_keys.rs b/utils/generate_test_keys.rs index cdefb62..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: &verifiable::ring_vrf_impl::EncodedPublicKey, 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); - } +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); + } } 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.0); - 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.0); - } - - 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); +}