diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c8b672..22f7a0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.6.0] - 2026-09-26 Breaking release. Every entry under Changed alters the public API. `BandersnatchSha512Ell2` also changes its outputs under the same `SUITE_ID`: @@ -19,6 +19,23 @@ proofs and ring commitments made with 0.5.3 do not verify. ### Changed +- Proving and verification are functions of the proof types. The `Prover` and + `Verifier` traits of the four schemes are gone: `Proof::prove(ios, ad, + &secret)` (the Ring one also takes the `RingProver`) and + `proof.verify(ios, ad, key)`, where the key is the `Public` for Tiny and + Thin, nothing for Pedersen and the `RingVerifier` for Ring. The keys keep + shortcuts: `Secret::prove_tiny`, `prove_thin`, `prove_pedersen`, + `prove_ring`, `Public::verify_tiny` and `verify_thin`. The Thin and Ring + `BatchItem::new` and `BatchVerifier::push` take the key last. +- Ring members are `Public` keys. `RingSetup::keys`, `prover_key`, + `verifier_key` and `VerifierKeyBuilder::append` take any iterator of + `Public` or `&Public` (for example `&ring`) instead of `&[AffinePoint]`, so + the subgroup check of a key happens once, when the key is decoded. + `Public::padding()` gives the ring padding point as a key. `==` on + `Public`, `Input` and `Output` no longer needs `PartialEq` on the suite + type, so it works in code generic over the suite. +- `Error` is `#[non_exhaustive]`: a `match` on it needs a wildcard arm, and + a new variant is no longer a breaking change. - Opaque types. `Public`, `Input` and `Output` are aliases of `PointWrapper`. Its point and the fields of the proof types, of `RingSetup` and of `RingContext` are private, with accessors. @@ -67,6 +84,10 @@ proofs and ring commitments made with 0.5.3 do not verify. arkworks BLS12-381 decoder accepts. Call `Valid::check` after decoding a `RingVerifierKey`, `RingCommitment` or `PcsVerifierParams` from untrusted bytes. +- `VerifierKeyBuilder` deserialization rejects a padding point other than + `RingSuite::PADDING` and a capacity that no PIOP domain gives. A builder + still defines the ring: load it only from a source trusted like a verifier + key. ### Performance @@ -317,6 +338,7 @@ of the Bandersnatch VRF specification. - `no_std` support. - `parallel` and `asm` optimization features. +[0.6.0]: https://github.com/davxy/ark-vrf/compare/v0.5.3...v0.6.0 [0.5.3]: https://github.com/davxy/ark-vrf/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/davxy/ark-vrf/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/davxy/ark-vrf/compare/v0.5.0...v0.5.1 diff --git a/Cargo.toml b/Cargo.toml index 19c7d7d..23115c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ark-vrf" -version = "0.5.3" +version = "0.6.0" edition = "2024" rust-version = "1.85" authors = [ "Davide Galassi " ] diff --git a/README.md b/README.md index e4c14a1..e9468f7 100644 --- a/README.md +++ b/README.md @@ -67,23 +67,22 @@ Compact VRF-AD producing a short `(c, s)` proof. _Prove_ ```rust,ignore -use ark_vrf::tiny::Prover; - let io = secret.vrf_io(input); // Generate a proof that binds the input-output pair and auxiliary data -let proof = secret.prove(io, b"aux data"); +let proof = TinyProof::prove(io, b"aux data", &secret); ``` _Verify_ ```rust,ignore -use ark_vrf::tiny::Verifier; - // Verify the proof against the public key -let result = public.verify(io, b"aux data", &proof); +let result = proof.verify(io, b"aux data", &public); assert!(result.is_ok()); ``` +The keys have the same operations as methods: `secret.prove_tiny(io, ad)` and +`public.verify_tiny(io, ad, &proof)`. + ### Thin-VRF The Thin VRF merges the public-key Schnorr pair and the VRF I/O pair into a @@ -92,30 +91,27 @@ proof (R, s). _Prove_ ```rust,ignore -use ark_vrf::thin::Prover; - let io = secret.vrf_io(input); -let proof = secret.prove(io, b"aux data"); +let proof = ThinProof::prove(io, b"aux data", &secret); ``` _Verify_ ```rust,ignore -use ark_vrf::thin::Verifier; - -let result = public.verify(io, b"aux data", &proof); +let result = proof.verify(io, b"aux data", &public); assert!(result.is_ok()); ``` +As for Tiny, the keys have the same operations as methods: `secret.prove_thin` +and `public.verify_thin`. + _Batch verify_ ```rust,ignore -use ark_vrf::thin::{Prover, BatchVerifier}; +let proof1 = ThinProof::prove(io, b"data1", &secret); +let proof2 = ThinProof::prove(io, b"data2", &secret); -let proof1 = secret.prove(io, b"data1"); -let proof2 = secret.prove(io, b"data2"); - -let mut batch = BatchVerifier::new(); -batch.push(&public, io, b"data1", &proof1); -batch.push(&public, io, b"data2", &proof2); +let mut batch = ThinBatchVerifier::new(); +batch.push(io, b"data1", &proof1, &public); +batch.push(io, b"data2", &proof2, &public); assert!(batch.verify().is_ok()); ``` @@ -125,12 +121,10 @@ Key-hiding VRF that replaces the public key with a Pedersen commitment to the se _Prove_ ```rust,ignore -use ark_vrf::pedersen::Prover; - let io = secret.vrf_io(input); // Generate a proof with a blinding factor -let (proof, blinding) = secret.prove(io, b"aux data"); +let (proof, blinding) = PedersenProof::prove(io, b"aux data", &secret); // The proof includes a commitment to the public key let key_commitment = proof.key_commitment(); @@ -139,12 +133,12 @@ let key_commitment = proof.key_commitment(); _Verify_ ```rust,ignore use ark_ec::CurveGroup; -use ark_vrf::pedersen::{PedersenSuite, Verifier}; +use ark_vrf::pedersen::PedersenSuite; // Verify without knowing which specific public key was used. // Verifies that the secret key used to generate `output` is the same as // the secret key used to generate `proof.key_commitment()`. -let result = Public::verify(io, b"aux data", &proof); +let result = proof.verify(io, b"aux data"); assert!(result.is_ok()); // Verify the proof was created using a specific public key. @@ -153,6 +147,9 @@ let expected = (public.point() + BandersnatchSha512Ell2::BLINDING_BASE * blindin assert_eq!(proof.key_commitment(), expected); ``` +The secret key has the same operation as a method: `secret.prove_pedersen`. +Verification needs no public key, so `Public` has no Pedersen method. + ### Ring-VRF The Ring VRF provides anonymity within a set of public keys using zero-knowledge proofs. @@ -167,15 +164,15 @@ let mut ring = (0..RING_SIZE) .map(|i| { let mut seed = [0u8; 32]; seed[..8].copy_from_slice(&i.to_le_bytes()); - Secret::from_seed(seed).public().point() + Secret::from_seed(seed).public() }) .collect::>(); // Patch the ring with the public key of the prover -ring[prover_key_index] = public.point(); +ring[prover_key_index] = public; // Any key can be replaced with the padding point -ring[0] = RingSetup::padding_point(); +ring[0] = Public::padding(); // Create parameters for the ring proof system. // These parameters are reusable across multiple proofs. @@ -201,8 +198,6 @@ let ring_setup = RingSetup::from_pcs_params(RING_SIZE, pcs_params).unwrap(); _Prove_ ```rust,ignore -use ark_vrf::ring::Prover; - // Create a prover key specific to this ring let prover_key = ring_setup.prover_key(&ring).unwrap(); @@ -217,13 +212,11 @@ let io = secret.vrf_io(input); // Generate a zero-knowledge proof that: // 1. The prover knows a secret key for one of the public keys in the ring // 2. That secret key was used to generate the VRF output -let proof = secret.prove(io, b"aux data", &prover); +let proof = RingProof::prove(io, b"aux data", &secret, &prover); ``` _Verify_ ```rust,ignore -use ark_vrf::ring::Verifier; - // Create a verifier key for this ring let verifier_key = ring_setup.verifier_key(&ring).unwrap(); @@ -235,9 +228,11 @@ let verifier = ring_ctx.ring_verifier(verifier_key); // 1. The proof was created by someone who knows a secret key in the ring // 2. The VRF output is correct for the given input // But it does NOT reveal which ring member created the proof -let result = Public::verify(io, b"aux data", &proof, &verifier); +let result = proof.verify(io, b"aux data", &verifier); ``` +The secret key has the same operation as a method: `secret.prove_ring`. + _Both keys_ ```rust,ignore // A party that needs both keys indexes the ring once. The two calls above diff --git a/benches/SUMMARY.md b/benches/SUMMARY.md index 2ffcc45..f5341a9 100644 --- a/benches/SUMMARY.md +++ b/benches/SUMMARY.md @@ -1,11 +1,12 @@ # Benchmark Baseline Suite: `Bandersnatch-SHA512-ELL2-v1` (Twisted Edwards on BLS12-381) -Date: 2026-09-21 +Date: 2026-09-21; the Ring VRF sections 2026-09-26 at `f079e25` (ring members +as `Public`), Rust 1.98.1 Features: `bandersnatch`, `ring`, `asm` (no `parallel`) Backend: `w3f-ring-proof` 0.0.10 (crates.io) -Criterion: 0.5.1, `--quick` mode. One run per benchmark, except `ring_prove` and -`ring_verify`, which are medians of four runs. +Criterion: 0.5.1, `--quick` mode. One run per benchmark. In the 2026-09-21 run, +`ring_prove` and `ring_verify` were medians of four runs. ## Machine @@ -64,29 +65,29 @@ Criterion: 0.5.1, `--quick` mode. One run per benchmark, except `ring_prove` and | Benchmark | n=255 | n=1023 | n=2047 | |:-------------------------|----------:|----------:|----------:| -| ring_params_setup | 849.7 us | 3.646 ms | 7.843 ms | -| ring_context_setup | 833.9 us | 3.649 ms | 8.304 ms | -| ring_prover_key | 36.85 ms | 115.2 ms | 212.4 ms | -| ring_verifier_key | 37.71 ms | 115.9 ms | 215.8 ms | -| ring_keys | 38.09 ms | 117.2 ms | 214.6 ms | -| ring_prove | 130.7 ms | 407.0 ms | 741.6 ms | -| ring_verify | 3.241 ms | 3.255 ms | 3.138 ms | -| ring_verifier_from_key | 252.3 us | 271.5 us | 283.9 us | -| ring_vk_from_commitment | 42.26 ns | 42.14 ns | 39.39 ns | -| ring_vk_builder_create | 306.8 ms | 1.385 s | 2.960 s | -| ring_vk_builder_append | 13.34 ms | 41.51 ms | 70.20 ms | -| ring_vk_builder_finalize | 76.07 ns | 78.15 ns | 82.57 ns | +| ring_params_setup | 797.1 us | 3.874 ms | 8.311 ms | +| ring_context_setup | 795.3 us | 3.854 ms | 7.792 ms | +| ring_prover_key | 40.24 ms | 120.1 ms | 220.7 ms | +| ring_verifier_key | 36.42 ms | 119.1 ms | 218.5 ms | +| ring_keys | 39.33 ms | 119.4 ms | 218.8 ms | +| ring_prove | 132.0 ms | 404.8 ms | 764.3 ms | +| ring_verify | 3.255 ms | 3.210 ms | 3.353 ms | +| ring_verifier_from_key | 250.5 us | 271.2 us | 303.7 us | +| ring_vk_from_commitment | 42.21 ns | 42.47 ns | 42.22 ns | +| ring_vk_builder_create | 304.6 ms | 1.379 s | 3.081 s | +| ring_vk_builder_append | 14.17 ms | 41.69 ms | 75.46 ms | +| ring_vk_builder_finalize | 79.85 ns | 79.86 ns | 79.85 ns | ### Batch Verification (ring size = 1023) | Benchmark | n=1 | n=2 | n=4 | n=8 | n=16 | n=32 | n=64 | n=128 | n=256 | |:-------------------|----------|----------|----------|----------|----------|----------|----------|----------|----------| -| batch_verifier_new | 2.147 us | - | - | - | - | - | - | - | - | -| batch_push | 45.75 us | 98.63 us | 197.3 us | 422.7 us | 830.4 us | 1.753 ms | 3.343 ms | 6.692 ms | 13.34 ms | -| batch_prepare_seq | 42.16 us | 86.89 us | 171.6 us | 389.9 us | 764.7 us | 1.623 ms | 3.127 ms | 6.581 ms | 12.52 ms | -| batch_prepare_par | 42.04 us | 82.68 us | 122.6 us | 157.2 us | 250.8 us | 263.5 us | 225.0 us | 527.2 us | 871.6 us | -| batch_push_prepared| 4.096 us | 7.388 us | 15.27 us | 31.00 us | 60.07 us | 118.7 us | 238.3 us | 489.8 us | 920.8 us | -| batch_verify | 3.353 ms | 3.935 ms | 5.443 ms | 7.656 ms | 11.73 ms | 18.18 ms | 30.46 ms | 49.66 ms | 82.87 ms | +| batch_verifier_new | 1.995 us | - | - | - | - | - | - | - | - | +| batch_push | 46.57 us | 91.54 us | 196.1 us | 405.1 us | 824.9 us | 1.759 ms | 3.558 ms | 6.744 ms | 14.08 ms | +| batch_prepare_seq | 41.88 us | 89.01 us | 180.1 us | 367.0 us | 761.1 us | 1.633 ms | 3.274 ms | 6.229 ms | 13.14 ms | +| batch_prepare_par | 44.57 us | 79.33 us | 117.1 us | 174.5 us | 247.3 us | 265.4 us | 203.0 us | 585.1 us | 835.0 us | +| batch_push_prepared| 4.047 us | 7.357 us | 14.60 us | 30.87 us | 63.83 us | 125.3 us | 244.1 us | 505.7 us | 991.1 us | +| batch_verify | 3.322 ms | 4.159 ms | 5.146 ms | 8.264 ms | 11.45 ms | 18.54 ms | 28.03 ms | 48.91 ms | 86.33 ms | ## Straus MSM (`straus.rs`) @@ -107,57 +108,61 @@ Optimal window size is w=2 for n=2 and n=3, and w=1 for n>=4. ### Ring Operations -- `ring_verify` is roughly constant across ring sizes (~3.2 ms) since verification +- `ring_verify` is roughly constant across ring sizes (~3.3 ms) since verification cost depends on the PIOP domain size, which stays the same for all three sizes tested (they all round up to the same power-of-two domain). -- `ring_prove` scales with ring size: 131 ms at n=255, 407 ms at n=1023, - 742 ms at n=2047. +- `ring_prove` scales with ring size: 132 ms at n=255, 405 ms at n=1023, + 764 ms at n=2047. - `ring_keys` costs one indexing pass, the same as either single-key method, so a party that needs both keys pays about half of the two calls. -- `ring_vk_builder_create` is the most expensive operation (up to 2.96 s at n=2047). +- Taking the ring members as `Public` keys (one extra copy of the points) + changes no key row beyond the run-to-run noise: rows that the change does + not touch, such as `ring_params_setup` and `ring_vk_builder_create`, moved + by up to 6% between the two runs too. +- `ring_vk_builder_create` is the most expensive operation (up to 3.08 s at n=2047). This is the Lagrangian SRS computation. - `ring_vk_builder_finalize` and `ring_vk_from_commitment` are essentially free (sub-100 ns). -- `ring_context_setup` and `ring_params_setup` have similar cost (~0.84 ms at n=255, - ~3.6 ms at n=1023, ~8.1 ms at n=2047), confirming that `RingContext` construction +- `ring_context_setup` and `ring_params_setup` have similar cost (~0.80 ms at n=255, + ~3.9 ms at n=1023, ~8.1 ms at n=2047), confirming that `RingContext` construction is dominated by PIOP domain setup with no SRS overhead. ### Batch Verification vs Simple Verification Simple verification cost for n proofs (ring size 1023): -`ring_verifier_from_key` (272 us) + n * `ring_verify` (3.26 ms). +`ring_verifier_from_key` (271 us) + n * `ring_verify` (3.21 ms). Batch verification combines multiple pairing checks into a single multi-pairing (ring proof) and multiple Pedersen verifications into a single (5N+2)-point MSM. -The `prepare` step (~49 us/proof seq) computes only the Pedersen challenge hash and +The `prepare` step (~51 us/proof seq) computes only the Pedersen challenge hash and packages data for deferred verification -- no scalar multiplications. The Pedersen verification is deferred to `verify`, where it runs as a single batched MSM using random linear combination with independent random scalars per equation. The `verify` step includes both the ring batch multi-pairing and the Pedersen -batch MSM. A linear fit (n=8..256) gives ~8.2 ms base + ~0.30 ms per additional +batch MSM. A linear fit (n=8..256) gives ~7.3 ms base + ~0.31 ms per additional proof. The standalone Pedersen `batch_verify` slope over the same range is -~0.051 ms/proof, leaving ~0.25 ms/proof for the ring multi-pairing. +~0.051 ms/proof, leaving ~0.26 ms/proof for the ring multi-pairing. -Sequential marginal cost per proof: ~0.049 ms (prepare) + ~0.30 ms (verify) = ~0.35 ms, -or ~9.3x cheaper than simple verification (3.26 ms). With parallel prepare, the -per-proof prepare cost drops to ~3.4 us at n=256, giving ~0.30 ms marginal, or ~10.7x +Sequential marginal cost per proof: ~0.051 ms (prepare) + ~0.31 ms (verify) = ~0.36 ms, +or ~8.9x cheaper than simple verification (3.21 ms). With parallel prepare, the +per-proof prepare cost drops to ~3.3 us at n=256, giving ~0.32 ms marginal, or ~10.2x cheaper. Estimated total wall times and speedups: | n | Simple | Batch seq | Batch par | Speedup (seq) | Speedup (par) | |----:|------------:|------------:|------------:|--------------:|--------------:| -| 1 | 3.53 ms | 3.40 ms | 3.40 ms | 1.04x | 1.04x | -| 2 | 6.78 ms | 4.02 ms | 4.02 ms | 1.69x | 1.69x | -| 4 | 13.29 ms | 5.62 ms | 5.57 ms | 2.37x | 2.39x | -| 8 | 26.31 ms | 8.05 ms | 7.81 ms | 3.27x | 3.37x | -| 16 | 52.35 ms | 12.49 ms | 11.98 ms | 4.19x | 4.37x | -| 32 | 104.43 ms | 19.81 ms | 18.45 ms | 5.27x | 5.66x | -| 64 | 208.60 ms | 33.59 ms | 30.69 ms | 6.21x | 6.80x | -| 128 | 416.92 ms | 56.25 ms | 50.19 ms | 7.41x | 8.31x | -| 256 | 833.57 ms | 95.39 ms | 83.74 ms | 8.74x | 9.95x | +| 1 | 3.48 ms | 3.36 ms | 3.37 ms | 1.03x | 1.03x | +| 2 | 6.69 ms | 4.25 ms | 4.24 ms | 1.58x | 1.58x | +| 4 | 13.11 ms | 5.33 ms | 5.26 ms | 2.46x | 2.49x | +| 8 | 25.95 ms | 8.63 ms | 8.44 ms | 3.01x | 3.08x | +| 16 | 51.63 ms | 12.21 ms | 11.70 ms | 4.23x | 4.41x | +| 32 | 102.98 ms | 20.18 ms | 18.81 ms | 5.10x | 5.48x | +| 64 | 205.70 ms | 31.31 ms | 28.24 ms | 6.57x | 7.28x | +| 128 | 411.13 ms | 55.14 ms | 49.49 ms | 7.46x | 8.31x | +| 256 | 821.98 ms | 99.47 ms | 87.17 ms | 8.26x | 9.43x | ### Batch Verify Scaling @@ -165,17 +170,17 @@ The `batch_verify` step scales sublinearly in the number of proofs: | n | batch_verify | per-proof | |----:|-----------:|----------:| -| 1 | 3.35 ms | 3.35 ms | -| 2 | 3.93 ms | 1.97 ms | -| 4 | 5.44 ms | 1.36 ms | -| 8 | 7.66 ms | 0.96 ms | -| 16 | 11.73 ms | 0.73 ms | -| 32 | 18.18 ms | 0.57 ms | -| 64 | 30.46 ms | 0.48 ms | -| 128 | 49.66 ms | 0.39 ms | -| 256 | 82.87 ms | 0.32 ms | - -Amortized cost per proof drops from 3.35 ms (n=1) to 0.32 ms (n=256), roughly 10x. +| 1 | 3.32 ms | 3.32 ms | +| 2 | 4.16 ms | 2.08 ms | +| 4 | 5.15 ms | 1.29 ms | +| 8 | 8.26 ms | 1.03 ms | +| 16 | 11.45 ms | 0.72 ms | +| 32 | 18.54 ms | 0.58 ms | +| 64 | 28.03 ms | 0.44 ms | +| 128 | 48.91 ms | 0.38 ms | +| 256 | 86.33 ms | 0.34 ms | + +Amortized cost per proof drops from 3.32 ms (n=1) to 0.34 ms (n=256), roughly 10x. Two factors contribute: the fixed-cost ring multi-pairing base (~2.8 ms) amortized across all proofs, and the MSM itself which scales as O(n / log n) via Pippenger/bucket methods rather than O(n). diff --git a/benches/pedersen.rs b/benches/pedersen.rs index 56fd5a3..1ce8c39 100644 --- a/benches/pedersen.rs +++ b/benches/pedersen.rs @@ -4,42 +4,38 @@ mod bench_utils; use ark_std::UniformRand; use ark_vrf::{ AffinePoint, Input, Secret, - pedersen::{BatchItem, PedersenSuite}, + pedersen::{BatchItem, PedersenSuite, Proof}, }; use bench_utils::SuiteExt; use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; fn bench_pedersen_prove(c: &mut Criterion) { - use ark_vrf::pedersen::Prover; - let secret = Secret::::from_seed([0; 32]); let input = Input::::new(b"bench input data").unwrap(); let io = secret.vrf_io(input); let name = format!("{}/pedersen_prove", S::SUITE_NAME); c.bench_function(&name, |b| { - b.iter(|| secret.prove(black_box(io), b"ad")); + b.iter(|| Proof::prove(black_box(io), b"ad", &secret)); }); } fn bench_pedersen_verify(c: &mut Criterion) { - use ark_vrf::pedersen::{Prover, Verifier}; - let secret = Secret::::from_seed([0; 32]); let input = Input::::new(b"bench input data").unwrap(); let io = secret.vrf_io(input); - let (proof, _blinding) = secret.prove(io, b"ad"); + let (proof, _blinding) = Proof::prove(io, b"ad", &secret); let name = format!("{}/pedersen_verify", S::SUITE_NAME); c.bench_function(&name, |b| { - b.iter(|| ark_vrf::Public::::verify(black_box(io), b"ad", black_box(&proof)).unwrap()); + b.iter(|| black_box(&proof).verify(black_box(io), b"ad").unwrap()); }); } const BATCH_SIZES: &[usize] = &[1, 2, 4, 8, 16, 32, 64, 128, 256]; fn bench_pedersen_batch(c: &mut Criterion) { - use ark_vrf::pedersen::{BatchVerifier, Prover}; + use ark_vrf::pedersen::BatchVerifier; let secret = Secret::::from_seed([0; 32]); let max_batch_size = BATCH_SIZES[BATCH_SIZES.len() - 1]; @@ -50,7 +46,7 @@ fn bench_pedersen_batch(c: &mut Criterion) { let input = Input::::from_affine_unchecked(AffinePoint::::rand(&mut rng)); let io = secret.vrf_io(input); let ad = format!("ad-{i}").into_bytes(); - let (proof, _) = secret.prove(io, &ad); + let (proof, _) = Proof::prove(io, &ad, &secret); (io, ad, proof) }) .collect(); diff --git a/benches/ring.rs b/benches/ring.rs index dfc03d1..6f37f90 100644 --- a/benches/ring.rs +++ b/benches/ring.rs @@ -3,8 +3,8 @@ mod bench_utils; use ark_std::UniformRand; use ark_vrf::{ - AffinePoint, Input, Secret, VrfIo, - ring::{self, BatchItem, BatchVerifier, Prover, RingSuite, Verifier}, + AffinePoint, Input, Public, Secret, VrfIo, + ring::{self, BatchItem, BatchVerifier, Proof, RingSuite}, }; use bench_utils::SuiteExt; use criterion::{BatchSize, BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; @@ -15,7 +15,7 @@ const RING_SIZES: [usize; 3] = [255, 1023, 2047]; struct BenchSetup { secret: Secret, io: VrfIo, - ring: Vec>, + ring: Vec>, prover_idx: usize, ring_setup: ring::RingSetup, } @@ -28,10 +28,10 @@ fn make_ring_setup(ring_size: usize) -> BenchSetup { let io = secret.vrf_io(input); let prover_idx = 3; - let mut ring: Vec> = (0..ring_size) - .map(|_| AffinePoint::::rand(&mut rng)) + let mut ring: Vec> = (0..ring_size) + .map(|_| Public::from_affine_unchecked(AffinePoint::::rand(&mut rng))) .collect(); - ring[prover_idx] = public.point(); + ring[prover_idx] = public; let ring_setup = ring::RingSetup::::from_rand_insecure(ring_size, &mut rng); @@ -91,17 +91,16 @@ fn ring_benches(c: &mut Criterion) { }); let ring_ctx = setup.ring_setup.ring_context(); - let prover_key = setup.ring_setup.prover_key(&setup.ring).unwrap(); + let (prover_key, verifier_key) = setup.ring_setup.keys(&setup.ring).unwrap(); let prover = ring_ctx.ring_prover(prover_key, setup.prover_idx); c.benchmark_group(format!("{}/ring_prove", S::SUITE_NAME)) .sample_size(10) .bench_function(id.clone(), |b| { - b.iter(|| setup.secret.prove(setup.io, b"ad", black_box(&prover))); + b.iter(|| Proof::prove(setup.io, b"ad", &setup.secret, black_box(&prover))); }); - let proof = setup.secret.prove(setup.io, b"ad", &prover); - let verifier_key = setup.ring_setup.verifier_key(&setup.ring).unwrap(); + let proof = Proof::prove(setup.io, b"ad", &setup.secret, &prover); let commitment = verifier_key.commitment(); let verifier = ring_ctx.ring_verifier(verifier_key.clone()); @@ -109,13 +108,9 @@ fn ring_benches(c: &mut Criterion) { .sample_size(10) .bench_function(id.clone(), |b| { b.iter(|| { - as Verifier>::verify( - setup.io, - b"ad", - black_box(&proof), - black_box(&verifier), - ) - .unwrap() + black_box(&proof) + .verify(setup.io, b"ad", black_box(&verifier)) + .unwrap() }); }); @@ -176,7 +171,7 @@ fn batch_benches(c: &mut Criterion) { let setup = make_ring_setup::(1023); let ring_ctx = setup.ring_setup.ring_context(); - let prover_key = setup.ring_setup.prover_key(&setup.ring).unwrap(); + let (prover_key, verifier_key) = setup.ring_setup.keys(&setup.ring).unwrap(); let prover = ring_ctx.ring_prover(prover_key, setup.prover_idx); let max_batch_size = BATCH_SIZES[BATCH_SIZES.len() - 1]; @@ -189,7 +184,7 @@ fn batch_benches(c: &mut Criterion) { let input = Input::::from_affine_unchecked(AffinePoint::::rand(rng)); let io = setup.secret.vrf_io(input); let ad = format!("ad-{i}").into_bytes(); - let proof = setup.secret.prove(io, &ad, &prover); + let proof = Proof::prove(io, &ad, &setup.secret, &prover); let prev = completed.fetch_add(1, std::sync::atomic::Ordering::Relaxed); let prev_pct = prev * 10 / max_batch_size; let curr_pct = (prev + 1) * 10 / max_batch_size; @@ -200,7 +195,6 @@ fn batch_benches(c: &mut Criterion) { }) .collect(); - let verifier_key = setup.ring_setup.verifier_key(&setup.ring).unwrap(); let verifier = ring_ctx.ring_verifier(verifier_key); // batch_verifier_new: cost is independent of batch size, bench once. @@ -221,7 +215,7 @@ fn batch_benches(c: &mut Criterion) { || BatchVerifier::::new(&verifier), |mut bv| { for item in &batch_items[..batch_size] { - bv.push(&verifier, item.io, &item.ad, &item.proof).unwrap(); + bv.push(item.io, &item.ad, &item.proof, &verifier).unwrap(); } }, BatchSize::LargeInput, @@ -235,7 +229,7 @@ fn batch_benches(c: &mut Criterion) { b.iter(|| { let _: Vec<_> = batch_items[..batch_size] .iter() - .map(|item| BatchItem::::new(&verifier, item.io, &item.ad, &item.proof)) + .map(|item| BatchItem::::new(item.io, &item.ad, &item.proof, &verifier)) .collect(); }); }); @@ -247,7 +241,7 @@ fn batch_benches(c: &mut Criterion) { b.iter(|| { let _: Vec<_> = batch_items[..batch_size] .par_iter() - .map(|item| BatchItem::::new(&verifier, item.io, &item.ad, &item.proof)) + .map(|item| BatchItem::::new(item.io, &item.ad, &item.proof, &verifier)) .collect(); }); }); @@ -261,7 +255,7 @@ fn batch_benches(c: &mut Criterion) { let prepared = batch_items[..batch_size] .iter() .map(|item| { - BatchItem::::new(&verifier, item.io, &item.ad, &item.proof) + BatchItem::::new(item.io, &item.ad, &item.proof, &verifier) .unwrap() }) .collect::>(); @@ -281,7 +275,7 @@ fn batch_benches(c: &mut Criterion) { { let mut bv = BatchVerifier::::new(&verifier); for item in &batch_items[..batch_size] { - bv.push(&verifier, item.io, &item.ad, &item.proof).unwrap(); + bv.push(item.io, &item.ad, &item.proof, &verifier).unwrap(); } c.benchmark_group(format!("{}/batch_verify", S::SUITE_NAME)) diff --git a/benches/thin.rs b/benches/thin.rs index d7e8bb2..37ba41c 100644 --- a/benches/thin.rs +++ b/benches/thin.rs @@ -2,37 +2,33 @@ mod bench_utils; use ark_std::UniformRand; -use ark_vrf::{AffinePoint, Input, Secret, Suite}; +use ark_vrf::{AffinePoint, Input, Secret, Suite, thin::Proof}; use bench_utils::SuiteExt; use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; fn bench_thin_prove(c: &mut Criterion) { - use ark_vrf::thin::Prover; - let secret = Secret::::from_seed([0; 32]); let input = Input::::new(b"bench input data").unwrap(); let io = secret.vrf_io(input); let name = format!("{}/thin_prove", S::SUITE_NAME); c.bench_function(&name, |b| { - b.iter(|| secret.prove(black_box(io), b"ad")); + b.iter(|| Proof::prove(black_box(io), b"ad", &secret)); }); } fn bench_thin_verify(c: &mut Criterion) { - use ark_vrf::thin::{Prover, Verifier}; - let secret = Secret::::from_seed([0; 32]); let public = secret.public(); let input = Input::::new(b"bench input data").unwrap(); let io = secret.vrf_io(input); - let proof = secret.prove(io, b"ad"); + let proof = Proof::prove(io, b"ad", &secret); let name = format!("{}/thin_verify", S::SUITE_NAME); c.bench_function(&name, |b| { b.iter(|| { - public - .verify(black_box(io), b"ad", black_box(&proof)) + black_box(&proof) + .verify(black_box(io), b"ad", &public) .unwrap() }); }); @@ -41,7 +37,7 @@ fn bench_thin_verify(c: &mut Criterion) { const BATCH_SIZES: &[usize] = &[1, 2, 4, 8, 16, 32, 64, 128, 256]; fn bench_thin_batch(c: &mut Criterion) { - use ark_vrf::thin::{BatchItem, BatchVerifier, Prover}; + use ark_vrf::thin::{BatchItem, BatchVerifier}; let secret = Secret::::from_seed([0; 32]); let public = secret.public(); @@ -53,7 +49,7 @@ fn bench_thin_batch(c: &mut Criterion) { let input = Input::::from_affine_unchecked(AffinePoint::::rand(&mut rng)); let io = secret.vrf_io(input); let ad = format!("ad-{i}").into_bytes(); - let proof = secret.prove(io, &ad); + let proof = Proof::prove(io, &ad, &secret); (io, ad, proof) }) .collect(); @@ -70,7 +66,7 @@ fn bench_thin_batch(c: &mut Criterion) { b.iter(|| { let _: Vec<_> = batch_items[..batch_size] .iter() - .map(|(io, ad, proof)| BatchItem::::new(&public, *io, ad, proof)) + .map(|(io, ad, proof)| BatchItem::::new(*io, ad, proof, &public)) .collect(); }); }); @@ -78,7 +74,7 @@ fn bench_thin_batch(c: &mut Criterion) { { let mut bv = BatchVerifier::::new(); for (io, ad, proof) in &batch_items[..batch_size] { - bv.push(&public, *io, ad, proof); + bv.push(*io, ad, proof, &public); } c.benchmark_group(&verify_group) diff --git a/benches/tiny.rs b/benches/tiny.rs index 1b67ef8..2966fa6 100644 --- a/benches/tiny.rs +++ b/benches/tiny.rs @@ -1,37 +1,33 @@ #[macro_use] mod bench_utils; -use ark_vrf::{Input, Secret, Suite}; +use ark_vrf::{Input, Secret, Suite, tiny::Proof}; use bench_utils::SuiteExt; use criterion::{Criterion, black_box, criterion_group, criterion_main}; fn bench_tiny_prove(c: &mut Criterion) { - use ark_vrf::tiny::Prover; - let secret = Secret::::from_seed([0; 32]); let input = Input::::new(b"bench input data").unwrap(); let io = secret.vrf_io(input); let name = format!("{}/tiny_prove", S::SUITE_NAME); c.bench_function(&name, |b| { - b.iter(|| secret.prove(black_box(io), b"ad")); + b.iter(|| Proof::prove(black_box(io), b"ad", &secret)); }); } fn bench_tiny_verify(c: &mut Criterion) { - use ark_vrf::tiny::{Prover, Verifier}; - let secret = Secret::::from_seed([0; 32]); let public = secret.public(); let input = Input::::new(b"bench input data").unwrap(); let io = secret.vrf_io(input); - let proof = secret.prove(io, b"ad"); + let proof = Proof::prove(io, b"ad", &secret); let name = format!("{}/tiny_verify", S::SUITE_NAME); c.bench_function(&name, |b| { b.iter(|| { - public - .verify(black_box(io), b"ad", black_box(&proof)) + black_box(&proof) + .verify(black_box(io), b"ad", &public) .unwrap() }); }); diff --git a/src/lib.rs b/src/lib.rs index fc10082..736b0a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,6 +153,7 @@ pub type CurveConfig = as AffineRepr>::Config; /// Crate error type. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] pub enum Error { /// Proof verification failed. VerificationFailure, @@ -300,7 +301,7 @@ pub trait Suite: Copy { /// challenge products and, with `secret-split`, the split scalars. This is /// best effort: temporaries inside arkworks and the ring proof backend are /// not wiped. The Pedersen prover returns the blinding factor to the caller, -/// who owns it from then on (see [`pedersen::Prover::prove`]). +/// who owns it from then on (see [`pedersen::Proof::prove`]). /// /// Scalar multiplications over the secret run in variable time: the arkworks /// double-and-add loop follows the bits of the scalar. `secret-split` hides @@ -470,9 +471,18 @@ impl Secret { /// and do not reject trailing bytes. /// /// [`Self::point`] reads the affine point. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy)] pub struct PointWrapper(pub(crate) AffinePoint, PhantomData); +// Not derived: the derive would require `S: PartialEq` of the suite marker. +impl PartialEq for PointWrapper { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for PointWrapper {} + /// Role marker of [`Public`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PublicKind; @@ -692,7 +702,6 @@ macro_rules! suite_types { #[cfg(test)] mod tests { use super::*; - use crate::tiny::{Prover, Verifier}; use ark_ec::AffineRepr; use suites::testing::{Input, Secret, TestSuite}; use testing::{TEST_SEED, random_val}; @@ -903,17 +912,21 @@ mod tests { }; // 4. Verify the malicious proof - assert!(public.verify(malicious_io, ad.as_bytes(), &proof).is_ok()); + assert!( + public + .verify_tiny(malicious_io, ad.as_bytes(), &proof) + .is_ok() + ); // 5. Verify the honest proof still works let honest_io = VrfIo { input, output: honest_output, }; - let honest_proof = secret.prove(honest_io, ad.as_bytes()); + let honest_proof = secret.prove_tiny(honest_io, ad.as_bytes()); assert!( public - .verify(honest_io, ad.as_bytes(), &honest_proof) + .verify_tiny(honest_io, ad.as_bytes(), &honest_proof) .is_ok() ); diff --git a/src/pedersen.rs b/src/pedersen.rs index 432edf4..1f28b3d 100644 --- a/src/pedersen.rs +++ b/src/pedersen.rs @@ -10,7 +10,7 @@ //! //! ```rust,ignore //! use ark_ec::CurveGroup; -//! use ark_vrf::pedersen::{PedersenSuite, Prover, Verifier}; +//! use ark_vrf::pedersen::{PedersenSuite, Proof}; //! use ark_vrf::suites::bandersnatch::*; //! //! let secret = Secret::from_seed([0; 32]); @@ -18,11 +18,11 @@ //! let input = Input::new(b"example input").unwrap(); //! let io = secret.vrf_io(input); //! -//! // Proving -//! let (proof, blinding) = secret.prove(io, b"aux data"); +//! // Proving (`secret.prove_pedersen(io, b"aux data")` is the same) +//! let (proof, blinding) = Proof::prove(io, b"aux data", &secret); //! //! // Verification -//! let result = Public::verify(io, b"aux data", &proof); +//! let result = proof.verify(io, b"aux data"); //! //! // Unblinding: verify the proof was created using a specific public key //! let expected = (public.point() + BandersnatchSha512Ell2::BLINDING_BASE * blinding).into_affine(); @@ -69,7 +69,7 @@ pub trait PedersenSuite: Suite { /// - `s`: Response scalar for the secret key (`s = k + c * x`) /// - `sb`: Response scalar for the blinding factor (`sb = kb + c * b`) /// -/// Construct it with [`Prover::prove`] or by deserialization. Deserialization +/// Construct it with [`Proof::prove`] or by deserialization. Deserialization /// via [`CanonicalDeserialize`] includes subgroup checks for curve points, so /// every proof holds valid points unless built with a `deserialize_*_unchecked` /// method. @@ -130,8 +130,7 @@ impl Proof { } } -/// Trait for types that can generate Pedersen VRF proofs. -pub trait Prover { +impl Proof { /// Generate a proof for the given VRF I/O pairs and additional data. /// /// Multiple I/O pairs are delinearized into a single merged pair before proving. @@ -141,75 +140,25 @@ pub trait Prover { /// commitment `Yb` to the public key, which is what the scheme hides. The /// caller must keep it private and zeroize it after use. The prover /// zeroizes its other secret temporaries but not this returned value. - fn prove( - &self, - ios: impl AsRef<[VrfIo]>, - ad: impl AsRef<[u8]>, - ) -> (Proof, ScalarField); -} - -/// Trait for types that can verify Pedersen VRF proofs. -/// -/// Verifies that a VRF output is correctly derived from an input using a -/// committed public key, without revealing which specific public key was used. -/// -/// All curve points involved in verification (I/O pairs and proof points) -/// are assumed to be in the prime-order subgroup. This is guaranteed when -/// points are constructed through checked constructors ([`Input::from_affine`], -/// [`Output::from_affine`]) or through trusted operations like [`Input::new`] -/// (hash-to-curve) and [`Secret::vrf_io`]. Proof points are guaranteed valid -/// when deserialized via [`CanonicalDeserialize`] (which includes subgroup -/// checks) or produced by [`Prover::prove`]. -/// -/// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places -/// the burden of subgroup validation on the caller. Passing points with -/// cofactor components leads to undefined verification behavior. -/// -/// The group identity is checked unconditionally, for the key commitment and -/// for every I/O pair. Neither binds the proof to a signer: the opening of the -/// identity commitment is the public `(0, 0)`, and a pair holding the identity -/// is satisfied by every secret key. It stays a legal value for the nonce -/// commitments `R` and `Ok`, which commit to nothing, and `Ok` is necessarily -/// the identity when no I/O pair is supplied. -pub trait Verifier { - /// Verify a proof for the given VRF I/O pairs and additional data. - /// - /// Multiple I/O pairs are delinearized into a single merged pair before verifying. - /// - /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the - /// key commitment or any I/O pair point is the group identity, - /// `Err(Error::VerificationFailure)` otherwise. - /// - /// Subgroup membership of the points is not re-checked here. It is - /// guaranteed by the checked constructors and checked deserialization of - /// the point wrappers (see [`PointWrapper`]). - fn verify( - ios: impl AsRef<[VrfIo]>, - ad: impl AsRef<[u8]>, - proof: &Proof, - ) -> Result<(), Error>; -} - -impl Prover for Secret { - fn prove( - &self, + pub fn prove( ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, - ) -> (Proof, ScalarField) { + secret: &Secret, + ) -> (Self, ScalarField) { let (mut t, input) = utils::vrf_transcript_input::(DomSep::PedersenVrf, ios, ad); // Build blinding factor from T.fork() - let blinding = S::blinding(&self.scalar, t.clone()); + let blinding = S::blinding(&secret.scalar, t.clone()); // Yb = x*G + b*B = PK + b*B let bb = smul!(S::BLINDING_BASE, blinding); - let pk_com = (self.public.0.into_group() + bb).into_affine(); + let pk_com = (secret.public.0.into_group() + bb).into_affine(); // Absorb Yb into the transcript t.absorb_serialize(&pk_com); // Nonces from T.fork() - let mut k = S::nonce(&self.scalar, t.clone()); + let mut k = S::nonce(&secret.scalar, t.clone()); let mut kb = S::nonce(&blinding, t.clone()); // R = k*G + kb*B @@ -227,7 +176,7 @@ impl Prover for Secret { let c = S::challenge(&[&r, &ok], t); // s = k + c*x - let mut cx = c * self.scalar; + let mut cx = c * secret.scalar; let s = k + cx; // sb = kb + c*b let mut cb = c * blinding; @@ -246,21 +195,45 @@ impl Prover for Secret { }; (proof, blinding) } -} -impl Verifier for Public { - fn verify( - ios: impl AsRef<[VrfIo]>, - ad: impl AsRef<[u8]>, - proof: &Proof, - ) -> Result<(), Error> { + /// Verify the proof for the given VRF I/O pairs and additional data. + /// + /// Verifies that each VRF output is correctly derived from its input using + /// the public key committed in the proof, without revealing which public + /// key it is. Multiple I/O pairs are delinearized into a single merged pair + /// before verifying. + /// + /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the + /// key commitment or any I/O pair point is the group identity, + /// `Err(Error::VerificationFailure)` otherwise. + /// + /// All curve points involved in verification (I/O pairs and proof points) + /// are assumed to be in the prime-order subgroup. This is guaranteed when + /// points are constructed through checked constructors ([`Input::from_affine`], + /// [`Output::from_affine`]), checked deserialization (see [`PointWrapper`]) + /// or through trusted operations like [`Input::new`] (hash-to-curve) and + /// [`Secret::vrf_io`]. Proof points are guaranteed valid when deserialized + /// via [`CanonicalDeserialize`] (which includes subgroup checks) or produced + /// by [`Proof::prove`]. Subgroup membership is not re-checked here. + /// + /// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places + /// the burden of subgroup validation on the caller. Passing points with + /// cofactor components leads to undefined verification behavior. + /// + /// The group identity is checked unconditionally, for the key commitment and + /// for every I/O pair. Neither binds the proof to a signer: the opening of the + /// identity commitment is the public `(0, 0)`, and a pair holding the identity + /// is satisfied by every secret key. It stays a legal value for the nonce + /// commitments `R` and `Ok`, which commit to nothing, and `Ok` is necessarily + /// the identity when no I/O pair is supplied. + pub fn verify(&self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>) -> Result<(), Error> { let Proof { pk_com, r, ok, s, sb, - } = proof; + } = self; // Yb = 0 is the commitment of the opening (0, 0), which is public, so // anyone can satisfy Eq2 without knowing a secret. @@ -311,6 +284,17 @@ impl Verifier for Public { } } +impl Secret { + /// Generate a Pedersen VRF proof. Same as [`Proof::prove`]. + pub fn prove_pedersen( + &self, + ios: impl AsRef<[VrfIo]>, + ad: impl AsRef<[u8]>, + ) -> (Proof, ScalarField) { + Proof::prove(ios, ad, self) + } +} + /// Deferred Pedersen VRF verification data for batch verification. /// /// Stores the merged pair, the proof points and scalars, and the challenge. @@ -362,7 +346,7 @@ impl BatchItem { /// Collects multiple proofs and verifies them together via a single /// multi-scalar multiplication. /// -/// The same subgroup membership assumptions as [`Verifier`] apply to all +/// The same subgroup membership assumptions as [`Proof::verify`] apply to all /// points fed into the batch (I/O pairs and proof points). pub struct BatchVerifier { items: Vec>, @@ -498,14 +482,12 @@ pub(crate) mod testing { use crate::testing::{self as common, CheckPoint, SuiteExt, TEST_SEED, random_val}; pub fn prove_verify() { - use pedersen::{Prover, Verifier}; - let secret = Secret::::from_seed(TEST_SEED); let input = Input::from_affine_unchecked(random_val(None)); let io = secret.vrf_io(input); - let (proof, blinding) = secret.prove(io, b"foo"); - let result = Public::verify(io, b"foo", &proof); + let (proof, blinding) = secret.prove_pedersen(io, b"foo"); + let result = proof.verify(io, b"foo"); assert!(result.is_ok()); assert_eq!( @@ -515,18 +497,18 @@ pub(crate) mod testing { } pub fn batch_verify() { - use pedersen::{BatchItem, BatchVerifier, Prover, Verifier}; + use pedersen::{BatchItem, BatchVerifier}; let secret = Secret::::from_seed(TEST_SEED); let input = Input::from_affine_unchecked(random_val(None)); let io = secret.vrf_io(input); - let (proof1, _) = secret.prove(io, b"foo"); - let (proof2, _) = secret.prove(io, b"bar"); + let (proof1, _) = secret.prove_pedersen(io, b"foo"); + let (proof2, _) = secret.prove_pedersen(io, b"bar"); // Single-proof verification still works. - assert!(Public::verify(io, b"foo", &proof1).is_ok()); - assert!(Public::verify(io, b"bar", &proof2).is_ok()); + assert!(proof1.verify(io, b"foo").is_ok()); + assert!(proof2.verify(io, b"bar").is_ok()); // Batch using push. let mut batch = BatchVerifier::new(); @@ -555,14 +537,12 @@ pub(crate) mod testing { /// N=1 slice produces same proof as passing a single `VrfIo`. pub fn prove_verify_multi_single() { - use pedersen::{Prover, Verifier}; - let secret = Secret::::from_seed(TEST_SEED); let input = Input::from_affine_unchecked(random_val(None)); let io = secret.vrf_io(input); - let (proof_single, blinding_single) = secret.prove(io, b"foo"); - let (proof_slice, blinding_slice) = secret.prove([io], b"foo"); + let (proof_single, blinding_single) = secret.prove_pedersen(io, b"foo"); + let (proof_slice, blinding_slice) = secret.prove_pedersen([io], b"foo"); // Byte-identical proofs and blinding factors let encode = |p: &pedersen::Proof| { @@ -574,14 +554,12 @@ pub(crate) mod testing { assert_eq!(blinding_single, blinding_slice); // Cross-verification - assert!(Public::verify(io, b"foo", &proof_slice).is_ok()); - assert!(Public::verify([io], b"foo", &proof_single).is_ok()); + assert!(proof_slice.verify(io, b"foo").is_ok()); + assert!(proof_single.verify([io], b"foo").is_ok()); } /// N=3 multi proof: verify succeeds; tampered output/input/ad fails. pub fn prove_verify_multi() { - use pedersen::{Prover, Verifier}; - let secret = Secret::::from_seed(TEST_SEED); let mut ios: Vec> = (0..3u8) @@ -595,53 +573,50 @@ pub(crate) mod testing { output: Output::from_affine_unchecked(secret.public().0), }); - let (proof, _) = secret.prove(&ios[..], b"bar"); - assert!(Public::verify(&ios[..], b"bar", &proof).is_ok()); + let (proof, _) = secret.prove_pedersen(&ios[..], b"bar"); + assert!(proof.verify(&ios[..], b"bar").is_ok()); // Tamper: wrong output on ios[1] let mut bad_ios = ios.clone(); bad_ios[1].output = secret.output(ios[0].input); - assert!(Public::verify(&bad_ios[..], b"bar", &proof).is_err()); + assert!(proof.verify(&bad_ios[..], b"bar").is_err()); // Tamper: wrong input on ios[0] let mut bad_ios = ios.clone(); bad_ios[0].input = ios[1].input; - assert!(Public::verify(&bad_ios[..], b"bar", &proof).is_err()); + assert!(proof.verify(&bad_ios[..], b"bar").is_err()); // Tamper: wrong ad - assert!(Public::verify(&ios[..], b"baz", &proof).is_err()); + assert!(proof.verify(&ios[..], b"baz").is_err()); } /// N=0 reduces to a Schnorr signature over the additional data. pub fn prove_verify_multi_empty() { - use pedersen::{Prover, Verifier}; - let secret = Secret::::from_seed(TEST_SEED); let ios: [VrfIo; 0] = []; - let (proof, _) = secret.prove(ios, b"bar"); + let (proof, _) = secret.prove_pedersen(ios, b"bar"); - assert!(Public::verify(ios, b"bar", &proof).is_ok()); + assert!(proof.verify(ios, b"bar").is_ok()); // Wrong ad should fail - assert!(Public::verify(ios, b"baz", &proof).is_err()); + assert!(proof.verify(ios, b"baz").is_err()); } /// With an empty I/O list `Ok` is the identity, which arkworks reads from /// several byte strings. One proof must have one encoding. pub fn proof_encoding_is_canonical() { use ark_serialize::Compress; - use pedersen::{Prover, Verifier}; let secret = Secret::::from_seed(TEST_SEED); let ios: [VrfIo; 0] = []; - let (proof, _) = secret.prove(ios, b"foo"); + let (proof, _) = secret.prove_pedersen(ios, b"foo"); assert!(proof.ok.is_zero()); let mut bytes = Vec::new(); proof.serialize_compressed(&mut bytes).unwrap(); let decoded = Proof::::deserialize_compressed(&bytes[..]).unwrap(); - assert!(Public::verify(ios, b"foo", &decoded).is_ok()); + assert!(decoded.verify(ios, b"foo").is_ok()); let mut reencoded = Vec::new(); decoded.serialize_compressed(&mut reencoded).unwrap(); assert_eq!(bytes, reencoded); @@ -666,15 +641,15 @@ pub(crate) mod testing { /// plain sum in `utils::common`. pub fn prove_verify_multi_msm() { use crate::utils::common::MSM_THRESHOLD; - use pedersen::{BatchVerifier, Prover, Verifier}; + use pedersen::BatchVerifier; let secret = Secret::::from_seed(TEST_SEED); let ios: Vec> = (0..MSM_THRESHOLD as u8) .map(|i| secret.vrf_io(Input::new(&[i]).unwrap())) .collect(); - let (proof, _) = secret.prove(&ios[..], b"msm"); - assert!(Public::verify(&ios[..], b"msm", &proof).is_ok()); + let (proof, _) = secret.prove_pedersen(&ios[..], b"msm"); + assert!(proof.verify(&ios[..], b"msm").is_ok()); let mut batch = BatchVerifier::new(); batch.push(&ios[..], b"msm", &proof); assert!(batch.verify().is_ok()); @@ -682,7 +657,7 @@ pub(crate) mod testing { // Tamper: wrong output on the last pair let mut bad_ios = ios.clone(); bad_ios[MSM_THRESHOLD - 1].output = ios[0].output; - assert!(Public::verify(&bad_ios[..], b"msm", &proof).is_err()); + assert!(proof.verify(&bad_ios[..], b"msm").is_err()); let mut batch = BatchVerifier::new(); batch.push(&bad_ios[..], b"msm", &proof); assert!(batch.verify().is_err()); @@ -697,7 +672,7 @@ pub(crate) mod testing { /// hides the bad pair behind a good one, where the merged pair alone is not /// enough to catch it. pub fn identity_io_pair_rejected() { - use pedersen::{BatchVerifier, Prover, Verifier}; + use pedersen::BatchVerifier; let identity_io = VrfIo:: { input: Input::from_affine_unchecked(AffinePoint::::zero()), @@ -707,8 +682,8 @@ pub(crate) mod testing { for seed in [TEST_SEED, [0x11; 32]] { let secret = Secret::::from_seed(seed); - let (proof, _) = secret.prove([identity_io], b"forgery"); - assert!(Public::verify([identity_io], b"forgery", &proof).is_err()); + let (proof, _) = secret.prove_pedersen([identity_io], b"forgery"); + assert!(proof.verify([identity_io], b"forgery").is_err()); let mut batch = BatchVerifier::new(); batch.push([identity_io], b"forgery", &proof); @@ -716,8 +691,8 @@ pub(crate) mod testing { let good_io = secret.vrf_io(Input::new(b"good").unwrap()); let ios = [good_io, identity_io]; - let (proof, _) = secret.prove(ios, b"forgery"); - assert!(Public::verify(ios, b"forgery", &proof).is_err()); + let (proof, _) = secret.prove_pedersen(ios, b"forgery"); + assert!(proof.verify(ios, b"forgery").is_err()); let mut batch = BatchVerifier::new(); batch.push(ios, b"forgery", &proof); @@ -732,7 +707,7 @@ pub(crate) mod testing { /// the nonces themselves, since the challenge multiplies zero. Both /// verification equations hold, so only an explicit check keeps it out. pub fn identity_key_commitment_rejected() { - use pedersen::{BatchVerifier, Verifier}; + use pedersen::BatchVerifier; let ios: [VrfIo; 0] = []; let ad = b"forgery"; @@ -754,7 +729,7 @@ pub(crate) mod testing { sb: kb, }; - assert!(Public::verify(ios, ad, &proof).is_err()); + assert!(proof.verify(ios, ad).is_err()); let mut batch = BatchVerifier::new(); batch.push(ios, ad, &proof); @@ -864,14 +839,13 @@ pub(crate) mod testing { } fn new(comment: &str, seed: &[u8; 32], alpha: &[u8], ad: &[u8]) -> Self { - use super::Prover; let base = common::TestVector::new(comment, seed, alpha, ad); let io = VrfIo { input: Input::::from_affine_unchecked(base.h), output: Output::from_affine_unchecked(base.gamma), }; let secret = Secret::from_scalar(base.sk); - let (proof, blind) = secret.prove(io, ad); + let (proof, blind) = secret.prove_pedersen(io, ad); Self { base, blind, proof } } @@ -934,7 +908,7 @@ pub(crate) mod testing { output: Output::from_affine_unchecked(self.base.gamma), }; let sk = Secret::from_scalar(self.base.sk); - let (proof, blind) = sk.prove(io, &self.base.ad); + let (proof, blind) = sk.prove_pedersen(io, &self.base.ad); assert_eq!(self.blind, blind, "Blinding factor mismatch"); assert_eq!(self.proof.pk_com, proof.pk_com, "Proof pkb mismatch"); assert_eq!(self.proof.r, proof.r, "Proof r mismatch"); @@ -942,7 +916,7 @@ pub(crate) mod testing { assert_eq!(self.proof.s, proof.s, "Proof s mismatch"); assert_eq!(self.proof.sb, proof.sb, "Proof sb mismatch"); - assert!(Public::verify(io, &self.base.ad, &proof).is_ok()); + assert!(proof.verify(io, &self.base.ad).is_ok()); } } } diff --git a/src/ring.rs b/src/ring.rs index 1b64a4f..a0ea5b4 100644 --- a/src/ring.rs +++ b/src/ring.rs @@ -23,7 +23,7 @@ //! //! ```rust,ignore //! use ark_vrf::suites::bandersnatch::*; -//! use ark_vrf::ring::{Prover, Verifier}; +//! use ark_vrf::ring::Proof; //! //! const RING_SIZE: usize = 100; //! let prover_key_index = 3; @@ -33,10 +33,10 @@ //! .map(|i| { //! let mut seed = [0u8; 32]; //! seed[..8].copy_from_slice(&i.to_le_bytes()); -//! Secret::from_seed(seed).public().point() +//! Secret::from_seed(seed).public() //! }) //! .collect::>(); -//! ring[prover_key_index] = public.point(); +//! ring[prover_key_index] = public; //! //! // Initialize ring parameters //! let ring_setup = RingSetup::from_seed_insecure(RING_SIZE, [0x42; 32]); @@ -46,12 +46,12 @@ //! let prover_key = ring_setup.prover_key(&ring).unwrap(); //! let prover = ring_ctx.ring_prover(prover_key, prover_key_index); //! let io = secret.vrf_io(input); -//! let proof = secret.prove(io, b"aux data", &prover); +//! let proof = Proof::prove(io, b"aux data", &secret, &prover); //! //! // Verification //! let verifier_key = ring_setup.verifier_key(&ring).unwrap(); //! let verifier = ring_ctx.ring_verifier(verifier_key); -//! let result = Public::verify(io, b"aux data", &proof, &verifier); +//! let result = proof.verify(io, b"aux data", &verifier); //! //! // Efficient verification with commitment //! let ring_commitment = verifier_key.commitment(); @@ -69,7 +69,7 @@ use ark_ec::{ pairing::Pairing, twisted_edwards::{Affine as TEAffine, TECurveConfig}, }; -use ark_std::{borrow::Cow, ops::Range}; +use ark_std::{borrow::Borrow, ops::Range}; use core::cell::Cell; use pedersen::{PedersenSuite, Proof as PedersenProof}; use utils::canonical::deserialize_canonical; @@ -187,7 +187,7 @@ pub type RingBareProof = ring_proof::RingProof, Kzg>; /// - `pedersen_proof`: Key commitment and VRF correctness proof /// - `ring_proof`: Membership proof binding the key commitment `Yb` to the ring /// -/// Construct it with [`Prover::prove`] or by deserialization. Deserialization +/// Construct it with [`Proof::prove`] or by deserialization. Deserialization /// via [`CanonicalDeserialize`] includes subgroup checks for curve points, so /// every proof holds valid points unless built with a `deserialize_*_unchecked` /// method. @@ -245,74 +245,19 @@ impl core::fmt::Debug for Proof { } } -/// Trait for types that can generate Ring VRF proofs. -pub trait Prover { +impl Proof { /// Generate a proof for the given VRF I/O pairs and additional data. /// /// Multiple I/O pairs are delinearized into a single merged pair before proving. - /// `prover` must be built for the ring and for the position of this key in - /// it (see [`RingContext::ring_prover`]). - fn prove( - &self, - ios: impl AsRef<[VrfIo]>, - ad: impl AsRef<[u8]>, - prover: &RingProver, - ) -> Proof; -} - -/// Trait for types that can verify Ring VRF proofs. -/// -/// Verifies that a VRF output was correctly derived using a secret key -/// belonging to one of the ring's public keys, without revealing which one. -/// -/// All curve points involved in verification (I/O pairs and proof points) -/// are assumed to be in the prime-order subgroup. This is guaranteed when -/// points are constructed through checked constructors ([`Input::from_affine`], -/// [`Output::from_affine`]) or through trusted operations like [`Input::new`] -/// (hash-to-curve) and [`Secret::vrf_io`]. Proof points are guaranteed valid -/// when deserialized via [`CanonicalDeserialize`] (which includes subgroup -/// checks) or produced by [`Prover::prove`]. -/// -/// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places -/// the burden of subgroup validation on the caller. Passing points with -/// cofactor components leads to undefined verification behavior. -/// -/// The group identity is checked unconditionally, for the key commitment and -/// for every I/O pair, by the embedded Pedersen verification (see -/// [`pedersen::Verifier`]). -pub trait Verifier { - /// Verify a proof for the given VRF I/O pairs and additional data. - /// - /// Multiple I/O pairs are delinearized into a single merged pair before verifying. - /// `verifier` must be built for the ring the proof claims membership in - /// (see [`RingContext::ring_verifier`]). - /// - /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the - /// key commitment or any I/O pair point is the group identity or the key - /// commitment cannot be mapped to Twisted Edwards form, - /// `Err(Error::VerificationFailure)` otherwise. - /// - /// Subgroup membership of the points is not re-checked here. It is - /// guaranteed by the checked constructors and checked deserialization of - /// the point wrappers (see [`PointWrapper`]). - fn verify( - ios: impl AsRef<[VrfIo]>, - ad: impl AsRef<[u8]>, - proof: &Proof, - verifier: &RingVerifier, - ) -> Result<(), Error>; -} - -impl Prover for Secret { - fn prove( - &self, + /// `ring_prover` must be built for the ring and for the position of the + /// key of `secret` in it (see [`RingContext::ring_prover`]). + pub fn prove( ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, + secret: &Secret, ring_prover: &RingProver, - ) -> Proof { - use pedersen::Prover as PedersenProver; - let (pedersen_proof, mut secret_blinding) = - >::prove(self, ios, ad); + ) -> Self { + let (pedersen_proof, mut secret_blinding) = PedersenProof::prove(ios, ad, secret); let ring_proof = ring_prover.prove(secret_blinding); secret_blinding.zeroize(); Proof { @@ -320,29 +265,67 @@ impl Prover for Secret { ring_proof, } } -} -impl Verifier for Public { - fn verify( + /// Verify the proof for the given VRF I/O pairs and additional data. + /// + /// Verifies that each VRF output was correctly derived using a secret key + /// belonging to one of the ring's public keys, without revealing which one. + /// Multiple I/O pairs are delinearized into a single merged pair before + /// verifying. `verifier` must be built for the ring the proof claims + /// membership in (see [`RingContext::ring_verifier`]). + /// + /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the + /// key commitment or any I/O pair point is the group identity or the key + /// commitment cannot be mapped to Twisted Edwards form, + /// `Err(Error::VerificationFailure)` otherwise. + /// + /// All curve points involved in verification (I/O pairs and proof points) + /// are assumed to be in the prime-order subgroup. This is guaranteed when + /// points are constructed through checked constructors ([`Input::from_affine`], + /// [`Output::from_affine`]), checked deserialization (see [`PointWrapper`]) + /// or through trusted operations like [`Input::new`] (hash-to-curve) and + /// [`Secret::vrf_io`]. Proof points are guaranteed valid when deserialized + /// via [`CanonicalDeserialize`] (which includes subgroup checks) or produced + /// by [`Proof::prove`]. Subgroup membership is not re-checked here. + /// + /// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places + /// the burden of subgroup validation on the caller. Passing points with + /// cofactor components leads to undefined verification behavior. + /// + /// The group identity is checked unconditionally, for the key commitment and + /// for every I/O pair, by the embedded Pedersen verification (see + /// [`pedersen::Proof::verify`]). + pub fn verify( + &self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, - proof: &Proof, verifier: &RingVerifier, ) -> Result<(), Error> { - use pedersen::Verifier as PedersenVerifier; - >::verify(ios, ad, &proof.pedersen_proof)?; - let key_commitment = proof + self.pedersen_proof.verify(ios, ad)?; + let key_commitment = self .pedersen_proof .key_commitment() .into_te() .ok_or(Error::InvalidData)?; - if !verifier.verify(proof.ring_proof.clone(), key_commitment) { + if !verifier.verify(self.ring_proof.clone(), key_commitment) { return Err(Error::VerificationFailure); } Ok(()) } } +impl Secret { + /// Generate a Ring VRF proof. Same as [`Proof::prove`]. + pub fn prove_ring( + &self, + ios: impl AsRef<[VrfIo]>, + ad: impl AsRef<[u8]>, + ring_prover: &RingProver, + ) -> Proof { + Proof::prove(ios, ad, self, ring_prover) + } +} + /// Lightweight ring proof context. /// /// Contains only the PIOP parameters needed to construct prover and verifier @@ -469,14 +452,35 @@ pub struct RingSetup { ring_ctx: RingContext, } -/// The ring proof backend asserts on the identity, so it is rejected here. -fn ring_members_te( - pks: &[AffinePoint], -) -> Result>]>, Error> { - if pks.iter().any(AffineRepr::is_zero) { - return Err(Error::InvalidData); +/// Collects at most `capacity` ring members as Twisted Edwards points. The +/// ring proof backend asserts on the identity, so it is rejected here. +fn ring_members_te>>( + pks: impl IntoIterator, + capacity: usize, +) -> Result>>, Error> { + let pks = pks.into_iter(); + let mut members = Vec::with_capacity(pks.size_hint().0.min(capacity)); + for pk in pks { + if members.len() == capacity { + return Err(Error::RingCapacityExceeded); + } + let point = pk.borrow().0; + if point.is_zero() { + return Err(Error::InvalidData); + } + members.push(point.into_te().ok_or(Error::InvalidData)?); + } + Ok(members) +} + +impl Public { + /// The padding point [`RingSuite::PADDING`] as a ring member. + /// + /// Nobody knows its discrete log, so it can take the place of any key in + /// the ring, for example the key of a removed member. + pub fn padding() -> Self { + Self::from_affine_unchecked(S::PADDING) } - TEMapping::to_te_slice(pks).ok_or(Error::InvalidData) } impl RingSetup { @@ -546,17 +550,20 @@ impl RingSetup { /// [`Self::verifier_key`] each drop one half, so a party that needs both /// keys pays twice if it calls them. /// + /// `pks` is any iterator of keys or of references to keys, for example + /// `&ring` for a `Vec>`. The keys are assumed to be in the + /// prime-order subgroup, which the checked constructors and the checked + /// deserialization of [`Public`] guarantee; a key built without a check + /// places that check on the caller. + /// /// Returns `Error::RingCapacityExceeded` if `pks` exceeds the max ring size, /// `Error::InvalidData` if a key is the identity or cannot be mapped to /// Twisted Edwards form. - pub fn keys( + pub fn keys>>( &self, - pks: &[AffinePoint], + pks: impl IntoIterator, ) -> Result<(RingProverKey, RingVerifierKey), Error> { - if pks.len() > self.ring_ctx.max_ring_size() { - return Err(Error::RingCapacityExceeded); - } - let pks = ring_members_te::(pks)?; + let pks = ring_members_te::(pks, self.ring_ctx.max_ring_size())?; Ok(ring_proof::index( &self.pcs_params, &self.ring_ctx.piop_params, @@ -566,23 +573,31 @@ impl RingSetup { /// Create a prover key for the given ring of public keys. /// - /// Use [`Self::keys`] if the verifier key is needed too. + /// Use [`Self::keys`] if the verifier key is needed too. `pks` is as for + /// [`Self::keys`]. /// /// Returns `Error::RingCapacityExceeded` if `pks` exceeds the max ring size, /// `Error::InvalidData` if a key is the identity or cannot be mapped to /// Twisted Edwards form. - pub fn prover_key(&self, pks: &[AffinePoint]) -> Result, Error> { + pub fn prover_key>>( + &self, + pks: impl IntoIterator, + ) -> Result, Error> { Ok(self.keys(pks)?.0) } /// Create a verifier key for the given ring of public keys. /// - /// Use [`Self::keys`] if the prover key is needed too. + /// Use [`Self::keys`] if the prover key is needed too. `pks` is as for + /// [`Self::keys`]. /// /// Returns `Error::RingCapacityExceeded` if `pks` exceeds the max ring size, /// `Error::InvalidData` if a key is the identity or cannot be mapped to /// Twisted Edwards form. - pub fn verifier_key(&self, pks: &[AffinePoint]) -> Result, Error> { + pub fn verifier_key>>( + &self, + pks: impl IntoIterator, + ) -> Result, Error> { Ok(self.keys(pks)?.1) } @@ -628,15 +643,6 @@ impl RingSetup { pub fn ring_context(&self) -> &RingContext { &self.ring_ctx } - - /// Get the padding point. - /// - /// This is a point of unknown dlog that can be used in place of any key during - /// ring construction. - #[inline(always)] - pub const fn padding_point() -> AffinePoint { - S::PADDING - } } /// Create a verifier key from a precomputed ring commitment and the PCS @@ -714,6 +720,12 @@ type PartialRingCommitment = /// /// Allows constructing a verifier key by adding public keys in batches, /// which is useful for large rings or memory-constrained environments. +/// +/// A serialized builder holds the ring commitment built so far, so it defines +/// the ring like a verifier key does. Load a builder only from a source that +/// you trust as much as a verifier key. Decoding rejects a padding point other +/// than [`RingSuite::PADDING`] and a capacity that no PIOP domain gives, but it +/// cannot check the commitment itself. #[derive(Clone, CanonicalSerialize)] pub struct VerifierKeyBuilder { partial: PartialRingCommitment, @@ -731,7 +743,16 @@ impl CanonicalDeserialize for VerifierKeyBuilder { compress, ark_serialize::Validate::No, )?; - if partial.curr_keys > partial.max_keys { + // A capacity is a power-of-two domain minus the overhead, checked + // without the overflow that rounding up a hostile value would cause. + let is_capacity = partial + .max_keys + .checked_add(dom_utils::piop_overhead::()) + .is_some_and(usize::is_power_of_two); + if !is_capacity + || partial.curr_keys > partial.max_keys + || S::PADDING.into_te() != Some(partial.padding) + { return Err(ark_serialize::SerializationError::InvalidData); } let pcs_params = PcsVerifierParams::::deserialize_with_mode( @@ -844,24 +865,21 @@ impl VerifierKeyBuilder { /// Add public keys to the ring being built. /// - /// On failure nothing is appended. Returns `Error::RingCapacityExceeded` if the + /// `pks` is as for [`RingSetup::keys`]. On failure nothing is appended. Returns `Error::RingCapacityExceeded` if the /// keys do not fit in the ring ([`Self::free_slots`] gives the remaining /// capacity), `Error::SrsLookupFailed` if the SRS lookup fails, /// `Error::InvalidData` if a key is the identity or cannot be mapped to /// Twisted Edwards form. - pub fn append( + pub fn append>>( &mut self, - pks: &[AffinePoint], + pks: impl IntoIterator, lookup: impl SrsLookup, ) -> Result<(), Error> { - if self.free_slots() < pks.len() { - return Err(Error::RingCapacityExceeded); - } + let pks = ring_members_te::(pks, self.free_slots())?; let srs = prefetched_lookup( lookup, self.partial.curr_keys..self.partial.curr_keys + pks.len(), )?; - let pks = ring_members_te::(pks)?; self.partial.append(&pks, srs); Ok(()) } @@ -895,10 +913,10 @@ impl BatchItem { /// Returns `Error::InvalidData` if the proof's key commitment cannot be /// mapped to Twisted Edwards form (e.g. identity point on SW-form suites). pub fn new( - verifier: &RingVerifier, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, proof: &Proof, + verifier: &RingVerifier, ) -> Result { let key_commitment = proof .pedersen_proof @@ -917,7 +935,7 @@ impl BatchItem { /// and verifies them together, amortizing the cost of pairing checks and /// multi-scalar multiplications. /// -/// The same subgroup membership assumptions as [`Verifier`] apply to all +/// The same subgroup membership assumptions as [`Proof::verify`] apply to all /// points fed into the batch (I/O pairs and proof points). pub struct BatchVerifier { ring_batch: RingBatchVerifier, @@ -953,12 +971,12 @@ impl BatchVerifier { /// mapped to Twisted Edwards form (e.g. identity point on SW-form suites). pub fn push( &mut self, - verifier: &RingVerifier, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, proof: &Proof, + verifier: &RingVerifier, ) -> Result<(), Error> { - let item = BatchItem::new(verifier, ios, ad, proof)?; + let item = BatchItem::new(ios, ad, proof, verifier)?; self.push_prepared(item); Ok(()) } @@ -1235,11 +1253,21 @@ pub(crate) mod testing { let io = secret.vrf_io(input); let ad_len = common::random_val::(Some(rng)) % (MAX_AD_LEN + 1); let ad = common::random_vec(ad_len, Some(rng)); - let proof = secret.prove(io, &ad, prover); + let proof = secret.prove_ring(io, &ad, prover); Self { io, ad, proof } } } + fn random_ring( + size: usize, + rng: &mut dyn ark_std::rand::RngCore, + ) -> Vec> { + common::random_vec::>(size, Some(rng)) + .into_iter() + .map(Public::from_affine_unchecked) + .collect() + } + #[allow(unused)] pub fn prove_verify() { let rng = &mut ark_std::test_rng(); @@ -1248,19 +1276,18 @@ pub(crate) mod testing { let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); - let mut pks = common::random_vec::>(TEST_RING_SIZE, Some(rng)); + let mut pks = random_ring::(TEST_RING_SIZE, rng); let prover_idx = 3; - pks[prover_idx] = public.0; + pks[prover_idx] = public; let ring_ctx = ring_setup.ring_context(); - let prover_key = ring_setup.prover_key(&pks).unwrap(); + let (prover_key, verifier_key) = ring_setup.keys(&pks).unwrap(); let prover = ring_ctx.ring_prover(prover_key, prover_idx); let item = TestItem::::new(&secret, &prover, rng); - let verifier_key = ring_setup.verifier_key(&pks).unwrap(); let verifier = ring_ctx.ring_verifier(verifier_key); - let result = Public::verify(item.io, &item.ad, &item.proof, &verifier); + let result = item.proof.verify(item.io, &item.ad, &verifier); assert!(result.is_ok()); } @@ -1268,26 +1295,26 @@ pub(crate) mod testing { /// several byte strings. The ring part goes through the same check. pub fn proof_encoding_is_canonical() { use ark_serialize::Compress; - use ring::{Prover, Verifier}; let rng = &mut ark_std::test_rng(); let ring_setup = RingSetup::::from_rand_insecure(TEST_RING_SIZE, rng); let secret = Secret::::from_seed(TEST_SEED); - let mut pks = common::random_vec::>(TEST_RING_SIZE, Some(rng)); + let mut pks = random_ring::(TEST_RING_SIZE, rng); let prover_idx = 3; - pks[prover_idx] = secret.public().0; + pks[prover_idx] = secret.public(); let ring_ctx = ring_setup.ring_context(); - let prover = ring_ctx.ring_prover(ring_setup.prover_key(&pks).unwrap(), prover_idx); - let verifier = ring_ctx.ring_verifier(ring_setup.verifier_key(&pks).unwrap()); + let (prover_key, verifier_key) = ring_setup.keys(&pks).unwrap(); + let prover = ring_ctx.ring_prover(prover_key, prover_idx); + let verifier = ring_ctx.ring_verifier(verifier_key); let ios: [VrfIo; 0] = []; - let proof = secret.prove(ios, b"foo", &prover); + let proof = secret.prove_ring(ios, b"foo", &prover); assert!(proof.pedersen_proof.ok.is_zero()); let mut bytes = Vec::new(); proof.serialize_compressed(&mut bytes).unwrap(); let decoded = Proof::::deserialize_compressed(&bytes[..]).unwrap(); - assert!(Public::verify(ios, b"foo", &decoded, &verifier).is_ok()); + assert!(decoded.verify(ios, b"foo", &verifier).is_ok()); let mut reencoded = Vec::new(); decoded.serialize_compressed(&mut reencoded).unwrap(); assert_eq!(bytes, reencoded); @@ -1310,7 +1337,7 @@ pub(crate) mod testing { let mut bytes = Vec::new(); proof.serialize_uncompressed(&mut bytes).unwrap(); let decoded = Proof::::deserialize_uncompressed(&bytes[..]).unwrap(); - assert!(Public::verify(ios, b"foo", &decoded, &verifier).is_ok()); + assert!(decoded.verify(ios, b"foo", &verifier).is_ok()); let ring_part = proof.pedersen_proof.uncompressed_size(); let first_point = ring_part..ring_part + G1Affine::::zero().uncompressed_size(); common::assert_aliases_rejected::>( @@ -1327,23 +1354,20 @@ pub(crate) mod testing { /// N=3 multi proof via ring prove/verify. #[allow(unused)] pub fn prove_verify_multi() { - use ring::{Prover, Verifier}; - let rng = &mut ark_std::test_rng(); let ring_setup = RingSetup::::from_rand_insecure(TEST_RING_SIZE, rng); let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); - let mut pks = common::random_vec::>(TEST_RING_SIZE, Some(rng)); + let mut pks = random_ring::(TEST_RING_SIZE, rng); let prover_idx = 3; - pks[prover_idx] = public.0; + pks[prover_idx] = public; let ring_ctx = ring_setup.ring_context(); - let prover_key = ring_setup.prover_key(&pks).unwrap(); + let (prover_key, verifier_key) = ring_setup.keys(&pks).unwrap(); let prover = ring_ctx.ring_prover(prover_key, prover_idx); - let verifier_key = ring_setup.verifier_key(&pks).unwrap(); let verifier = ring_ctx.ring_verifier(verifier_key); let mut ios: Vec> = (0..3u8) @@ -1357,16 +1381,16 @@ pub(crate) mod testing { output: Output::from_affine_unchecked(public.0), }); - let proof = secret.prove(&ios[..], b"bar", &prover); - assert!(Public::verify(&ios[..], b"bar", &proof, &verifier).is_ok()); + let proof = secret.prove_ring(&ios[..], b"bar", &prover); + assert!(proof.verify(&ios[..], b"bar", &verifier).is_ok()); // Tamper: wrong output on ios[1] let mut bad_ios = ios.clone(); bad_ios[1].output = secret.output(ios[0].input); - assert!(Public::verify(&bad_ios[..], b"bar", &proof, &verifier).is_err()); + assert!(proof.verify(&bad_ios[..], b"bar", &verifier).is_err()); // Tamper: wrong ad - assert!(Public::verify(&ios[..], b"baz", &proof, &verifier).is_err()); + assert!(proof.verify(&ios[..], b"baz", &verifier).is_err()); } #[allow(unused)] @@ -1381,12 +1405,12 @@ pub(crate) mod testing { let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); - let mut pks = common::random_vec::>(TEST_RING_SIZE, Some(rng)); + let mut pks = random_ring::(TEST_RING_SIZE, rng); let prover_idx = 3; - pks[prover_idx] = public.0; + pks[prover_idx] = public; let ring_ctx = ring_setup.ring_context(); - let prover_key = ring_setup.prover_key(&pks).unwrap(); + let (prover_key, verifier_key) = ring_setup.keys(&pks).unwrap(); let prover = ring_ctx.ring_prover(prover_key, prover_idx); // Generate proofs in parallel @@ -1397,7 +1421,6 @@ pub(crate) mod testing { }) .collect(); - let verifier_key = ring_setup.verifier_key(&pks).unwrap(); let verifier = ring_ctx.ring_verifier(verifier_key); // Batch verify all proofs @@ -1408,7 +1431,7 @@ pub(crate) mod testing { // Prove incrementally constructed batches for item in batch.iter() { batch_verifier - .push(&verifier, item.io, &item.ad, &item.proof) + .push(item.io, &item.ad, &item.proof, &verifier) .unwrap(); let res = batch_verifier.verify(); assert!(res.is_ok()); @@ -1418,7 +1441,7 @@ pub(crate) mod testing { let mut batch_verifier = BatchVerifier::::new(&verifier); let prepared: Vec<_> = batch .par_iter() - .map(|item| BatchItem::::new(&verifier, item.io, &item.ad, &item.proof).unwrap()) + .map(|item| BatchItem::::new(item.io, &item.ad, &item.proof, &verifier).unwrap()) .collect(); prepared .into_iter() @@ -1427,12 +1450,11 @@ pub(crate) mod testing { // Multi-ring batch: build a second ring sharing the same KZG SRS, // then aggregate proofs from both rings into a single batch verifier. - let mut pks_b = common::random_vec::>(TEST_RING_SIZE, Some(rng)); + let mut pks_b = random_ring::(TEST_RING_SIZE, rng); let prover_idx_b = 1; - pks_b[prover_idx_b] = public.0; - let prover_key_b = ring_setup.prover_key(&pks_b).unwrap(); + pks_b[prover_idx_b] = public; + let (prover_key_b, verifier_key_b) = ring_setup.keys(&pks_b).unwrap(); let prover_b = ring_ctx.ring_prover(prover_key_b, prover_idx_b); - let verifier_key_b = ring_setup.verifier_key(&pks_b).unwrap(); let verifier_b = ring_ctx.ring_verifier(verifier_key_b); let batch_b: Vec<_> = (0..TEST_RING_SIZE) @@ -1445,12 +1467,12 @@ pub(crate) mod testing { let mut batch_verifier = BatchVerifier::::new(&verifier); for item in batch.iter() { batch_verifier - .push(&verifier, item.io, &item.ad, &item.proof) + .push(item.io, &item.ad, &item.proof, &verifier) .unwrap(); } for item in batch_b.iter() { batch_verifier - .push(&verifier_b, item.io, &item.ad, &item.proof) + .push(item.io, &item.ad, &item.proof, &verifier_b) .unwrap(); } batch_verifier.verify().expect("multi-ring batch verifies"); @@ -1461,7 +1483,7 @@ pub(crate) mod testing { let mut batch_verifier = BatchVerifier::::new(&verifier); let item_b = &batch_b[0]; batch_verifier - .push(&verifier, item_b.io, &item_b.ad, &item_b.proof) + .push(item_b.io, &item_b.ad, &item_b.proof, &verifier) .unwrap(); assert!( batch_verifier.verify().is_err(), @@ -1477,7 +1499,7 @@ pub(crate) mod testing { let ring_setup = RingSetup::::from_rand_insecure(TEST_RING_SIZE, rng); let max_ring_size = ring_setup.ring_context().max_ring_size(); - let pks = common::random_vec::>(max_ring_size + 1, Some(rng)); + let pks = random_ring::(max_ring_size + 1, rng); assert!(matches!( ring_setup.prover_key(&pks), Err(Error::RingCapacityExceeded) @@ -1491,6 +1513,14 @@ pub(crate) mod testing { Err(Error::RingCapacityExceeded) )); + // The key functions stop at the capacity, so an endless iterator ends + // with an error, not with unbounded memory use. + let endless = core::iter::repeat(Public::::padding()); + assert!(matches!( + ring_setup.keys(endless), + Err(Error::RingCapacityExceeded) + )); + // SRS sized for `TEST_RING_SIZE` cannot back a ring beyond its capacity. let pcs_params = ring_setup.pcs_params.clone(); assert!(matches!( @@ -1505,8 +1535,8 @@ pub(crate) mod testing { let rng = &mut ark_std::test_rng(); let ring_setup = RingSetup::::from_rand_insecure(TEST_RING_SIZE, rng); - let mut pks = common::random_vec::>(TEST_RING_SIZE, Some(rng)); - pks[0] = AffinePoint::::zero(); + let mut pks = random_ring::(TEST_RING_SIZE, rng); + pks[0] = Public::from_affine_unchecked(AffinePoint::::zero()); assert!(matches!( ring_setup.prover_key(&pks), @@ -1551,18 +1581,16 @@ pub(crate) mod testing { where G1Affine: OffCurveAlias, { - use ring::Prover; - let rng = &mut ark_std::test_rng(); let ring_setup = RingSetup::::from_rand_insecure(TEST_RING_SIZE, rng); let secret = Secret::::from_seed(TEST_SEED); - let mut pks = common::random_vec::>(TEST_RING_SIZE, Some(rng)); + let mut pks = random_ring::(TEST_RING_SIZE, rng); let prover_idx = 3; - pks[prover_idx] = secret.public().0; + pks[prover_idx] = secret.public(); let ring_ctx = ring_setup.ring_context(); let prover = ring_ctx.ring_prover(ring_setup.prover_key(&pks).unwrap(), prover_idx); let input = Input::from_affine_unchecked(common::random_val(Some(rng))); - let proof = secret.prove(secret.vrf_io(input), b"foo", &prover); + let proof = secret.prove_ring(secret.vrf_io(input), b"foo", &prover); let point_len = G1Affine::::zero().uncompressed_size(); let replace_with_alias = |bytes: &mut [u8], start: usize| { @@ -1594,6 +1622,33 @@ pub(crate) mod testing { assert!(ark_serialize::Valid::check(&unchecked).is_err()); } + /// A builder checkpoint carries the padding point and the ring capacity. + /// The encoder writes the suite padding and a capacity that a PIOP domain + /// gives. Any other value must fail to decode, on the unchecked path too: + /// `append` would subtract the wrong padding from every key, and + /// `free_slots` would report a false capacity. + pub fn builder_decode_rejects_foreign_ring_shape() { + let (builder, _) = S::ring_setup().verifier_key_builder(); + let decode = |builder: &VerifierKeyBuilder| { + let mut bytes = Vec::new(); + builder.serialize_compressed(&mut bytes).unwrap(); + VerifierKeyBuilder::::deserialize_compressed_unchecked(&bytes[..]) + }; + assert!(decode(&builder).is_ok()); + + let mut foreign_padding = builder.clone(); + foreign_padding.partial.padding = S::BLINDING_BASE.into_te().unwrap(); + assert!(decode(&foreign_padding).is_err()); + + // `usize::MAX` would overflow a capacity computation that rounds up + // to the next power of two. + for max_keys in [builder.partial.max_keys + 1, usize::MAX] { + let mut foreign_capacity = builder.clone(); + foreign_capacity.partial.max_keys = max_keys; + assert!(decode(&foreign_capacity).is_err()); + } + } + /// The G1 length carries the ring capacity. A restored setup keeps its /// bytes and capacity; any other G1 length is a decode error, or a raw /// SRS file would decode as a setup of another domain. @@ -1662,6 +1717,11 @@ pub(crate) mod testing { // Check that the point is on curve. assert!(S::PADDING.check(true).is_ok()); + + // Callers put the padding in the ring in place of a missing key, so + // it must be a valid `Public`: in the subgroup and not the identity. + assert!(Public::::from_affine(S::PADDING).is_ok()); + assert_eq!(Public::::padding().point(), S::PADDING); } #[allow(unused)] @@ -1689,17 +1749,17 @@ pub(crate) mod testing { let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); - let mut pks = common::random_vec::>(TEST_RING_SIZE, Some(rng)); + let mut pks = random_ring::(TEST_RING_SIZE, rng); let prover_idx = 3; - pks[prover_idx] = public.0; + pks[prover_idx] = public; - let prover_key = ring_setup.prover_key(&pks).unwrap(); + let (prover_key, verifier_key) = ring_setup.keys(&pks).unwrap(); let prover = ring_setup .ring_context() .ring_prover(prover_key, prover_idx); let item = TestItem::::new(&secret, &prover, rng); - let commitment = ring_setup.verifier_key(&pks).unwrap().commitment(); + let commitment = verifier_key.commitment(); // Round-trip the params to mimic a verifier-only user holding just // the serialized params, the ring commitment and the ring size. @@ -1713,7 +1773,7 @@ pub(crate) mod testing { let ring_ctx = RingContext::::new(TEST_RING_SIZE); let verifier_key = super::verifier_key_from_commitment::(commitment, pcs_params); let verifier = ring_ctx.ring_verifier(verifier_key); - assert!(Public::verify(item.io, &item.ad, &item.proof, &verifier).is_ok()); + assert!(item.proof.verify(item.io, &item.ad, &verifier).is_ok()); } #[allow(unused)] @@ -1731,24 +1791,33 @@ pub(crate) mod testing { let ring_ctx = ring_setup.ring_context(); let ring_size = ring_ctx.max_ring_size(); let prover_idx = random_val::(Some(rng)) % ring_size; - let mut pks = random_vec::>(ring_size, Some(rng)); - pks[prover_idx] = public.0; + let mut pks = random_ring::(ring_size, rng); + pks[prover_idx] = public; let prover_key = ring_setup.prover_key(&pks).unwrap(); let prover = ring_ctx.ring_prover(prover_key, prover_idx); - let proof = secret.prove(io, b"foo", &prover); + let proof = secret.prove_ring(io, b"foo", &prover); // Incremental ring verifier key construction let (mut vk_builder, lookup) = ring_setup.verifier_key_builder(); assert_eq!(vk_builder.free_slots(), pks.len()); + + // `append` stops at the free slots, so an endless iterator ends with + // an error, not with unbounded memory use, and appends nothing. + let endless = core::iter::repeat(Public::::padding()); + assert_eq!( + vk_builder.append(endless, &lookup).unwrap_err(), + Error::RingCapacityExceeded + ); + assert_eq!(vk_builder.free_slots(), pks.len()); assert_eq!( vk_builder.pcs_verifier_params(), ring_setup.pcs_verifier_params() ); - let extra_pk = random_val::>(Some(rng)); + let extra_pk = Public::::from_affine_unchecked(random_val(Some(rng))); assert_eq!( - vk_builder.append(&[extra_pk], |_| None).unwrap_err(), + vk_builder.append([extra_pk], |_| None).unwrap_err(), Error::SrsLookupFailed ); @@ -1759,15 +1828,15 @@ pub(crate) mod testing { assert_eq!(vk_builder.free_slots(), pks.len()); } // No more space left; `free_slots` reports the remaining capacity. - let extra_pk = random_val::>(Some(rng)); + let extra_pk = Public::::from_affine_unchecked(random_val(Some(rng))); assert_eq!( - vk_builder.append(&[extra_pk], &lookup).unwrap_err(), + vk_builder.append([extra_pk], &lookup).unwrap_err(), Error::RingCapacityExceeded ); assert_eq!(vk_builder.free_slots(), 0); let verifier_key = vk_builder.finalize(); let verifier = ring_ctx.ring_verifier(verifier_key); - let result = Public::verify(io, b"foo", &proof, &verifier); + let result = proof.verify(io, b"foo", &verifier); assert!(result.is_ok()); } @@ -1904,6 +1973,11 @@ pub(crate) mod testing { $crate::ring::testing::off_curve_pairing_point_rejected::<$suite>() } + #[test] + fn builder_decode_rejects_foreign_ring_shape() { + $crate::ring::testing::builder_decode_rejects_foreign_ring_shape::<$suite>() + } + #[test] fn identity_in_ring_rejected() { $crate::ring::testing::identity_in_ring_rejected::<$suite>() @@ -1982,7 +2056,7 @@ pub(crate) mod testing { pub struct TestVector { pub pedersen: pedersen::testing::TestVector, - pub ring_pks: [AffinePoint; TEST_RING_SIZE], + pub ring_pks: [Public; TEST_RING_SIZE], pub ring_pks_com: RingCommitment, pub ring_proof: RingBareProof, } @@ -2005,7 +2079,6 @@ pub(crate) mod testing { } fn new(comment: &str, seed: &[u8; 32], alpha: &[u8], ad: &[u8]) -> Self { - use super::Prover; let pedersen = pedersen::testing::TestVector::new(comment, seed, alpha, ad); let secret = Secret::::from_scalar(pedersen.base.sk); @@ -2021,16 +2094,15 @@ pub(crate) mod testing { use ark_std::rand::SeedableRng; let rng = &mut ark_std::rand::rngs::StdRng::from_seed([42; 32]); let prover_idx = 3; - let mut ring_pks = common::random_vec::>(TEST_RING_SIZE, Some(rng)); - ring_pks[prover_idx] = public.0; + let mut ring_pks = random_ring::(TEST_RING_SIZE, rng); + ring_pks[prover_idx] = public; // Blinding is disabled to make the proof reproducible let ring_ctx = RingContext::::new_without_blinding(TEST_RING_SIZE); - let prover_key = ring_setup.prover_key(&ring_pks).unwrap(); + let (prover_key, verifier_key) = ring_setup.keys(&ring_pks).unwrap(); let prover = ring_ctx.into_ring_prover(prover_key, prover_idx); - let proof = secret.prove(io, ad, &prover); + let proof = secret.prove_ring(io, ad, &prover); - let verifier_key = ring_setup.verifier_key(&ring_pks).unwrap(); let ring_pks_com = verifier_key.commitment(); { @@ -2052,7 +2124,7 @@ pub(crate) mod testing { fn from_map(map: &common::TestVectorMap) -> Self { let pedersen = pedersen::testing::TestVector::from_map(map); - let ring_pks = map.get::<[AffinePoint; TEST_RING_SIZE]>("ring_pks"); + let ring_pks = map.get::<[Public; TEST_RING_SIZE]>("ring_pks"); let ring_pks_com = map.get::>("ring_pks_com"); let ring_proof = map.get::>("ring_proof"); @@ -2085,17 +2157,16 @@ pub(crate) mod testing { let ring_setup = ::ring_setup(); - let prover_idx = self.ring_pks.iter().position(|&pk| pk == public.0).unwrap(); + let prover_idx = self.ring_pks.iter().position(|pk| *pk == public).unwrap(); // Blinding is disabled to reproduce the exact proof in the vector let ring_ctx = RingContext::::new_without_blinding(TEST_RING_SIZE); - let prover_key = ring_setup.prover_key(&self.ring_pks).unwrap(); + let (prover_key, verifier_key) = ring_setup.keys(self.ring_pks).unwrap(); let prover = ring_ctx.ring_prover(prover_key, prover_idx); - let verifier_key = ring_setup.verifier_key(&self.ring_pks).unwrap(); let verifier = ring_ctx.ring_verifier(verifier_key); - let proof = secret.prove(io, &self.pedersen.base.ad, &prover); + let proof = secret.prove_ring(io, &self.pedersen.base.ad, &prover); { // Check if Pedersen proof matches @@ -2113,7 +2184,7 @@ pub(crate) mod testing { assert_eq!(p.0, p.1); } - assert!(Public::verify(io, &self.pedersen.base.ad, &proof, &verifier).is_ok()); + assert!(proof.verify(io, &self.pedersen.base.ad, &verifier).is_ok()); } } } diff --git a/src/thin.rs b/src/thin.rs index 9045bb2..135d616 100644 --- a/src/thin.rs +++ b/src/thin.rs @@ -8,7 +8,7 @@ //! //! ```rust,ignore //! use ark_vrf::suites::bandersnatch::*; -//! use ark_vrf::thin::{Prover, Verifier}; +//! use ark_vrf::thin::Proof; //! //! let secret = Secret::from_seed([0; 32]); //! let public = secret.public(); @@ -16,10 +16,14 @@ //! let io = secret.vrf_io(input); //! //! // Proving -//! let proof = secret.prove(io, b"aux data"); +//! let proof = Proof::prove(io, b"aux data", &secret); //! //! // Verification -//! let result = public.verify(io, b"aux data", &proof); +//! let result = proof.verify(io, b"aux data", &public); +//! +//! // The same, as methods of the keys +//! let proof = secret.prove_thin(io, b"aux data"); +//! let result = public.verify_thin(io, b"aux data", &proof); //! ``` use crate::{utils::canonical::deserialize_point, utils::common::DomSep, utils::weight_scalar, *}; @@ -37,7 +41,7 @@ impl ThinSuite for T where T: Suite {} /// - `r`: Nonce commitment on the merged input (`R = k * I_m`) /// - `s`: Response scalar (`s = k + c * x`) /// -/// Construct it with [`Prover::prove`] or by deserialization. Deserialization +/// Construct it with [`Proof::prove`] or by deserialization. Deserialization /// via [`CanonicalDeserialize`] includes subgroup checks for curve points, so /// every proof holds valid points unless built with a `deserialize_*_unchecked` /// method. @@ -92,62 +96,15 @@ fn vrf_transcript_scalars( utils::vrf_transcript_scalars_with_schnorr(DomSep::ThinVrf, public, ios, ad) } -/// Trait for types that can generate Thin VRF proofs. -pub trait Prover { +impl Proof { /// Generate a proof for the given VRF I/O pairs and additional data. /// /// Multiple I/O pairs are delinearized into a single merged pair before proving. - fn prove(&self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>) -> Proof; -} - -/// Trait for types that can verify Thin VRF proofs. -/// -/// Verifies that a VRF output is correctly derived from an input using the -/// secret key of the given public key. -/// -/// All curve points involved in verification (public key, I/O pairs, and proof -/// points) are assumed to be in the prime-order subgroup. This is guaranteed -/// when points are constructed through checked constructors ([`Public::from_affine`], -/// [`Input::from_affine`], [`Output::from_affine`]) or through trusted -/// operations like [`Input::new`] (hash-to-curve) and [`Secret::vrf_io`]. -/// Proof points are guaranteed valid when deserialized via [`CanonicalDeserialize`] -/// (which includes subgroup checks) or produced by [`Prover::prove`]. -/// -/// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places -/// the burden of subgroup validation on the caller. Passing points with -/// cofactor components leads to undefined verification behavior. -/// -/// The group identity is checked unconditionally, for the public key and for -/// every I/O pair. Neither binds the proof to a signer: the secret scalar of -/// the identity key is publicly known, and a pair holding the identity is -/// satisfied by every secret key. It stays a legal value for the nonce -/// commitment `R`, which commits to nothing. -pub trait Verifier { - /// Verify a proof for the given VRF I/O pairs and additional data. - /// - /// Multiple I/O pairs are delinearized into a single merged pair before verifying. - /// - /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the - /// public key or any I/O pair point is the group identity, - /// `Err(Error::VerificationFailure)` otherwise. - /// - /// Subgroup membership of the points is not re-checked here. It is - /// guaranteed by the checked constructors and checked deserialization of - /// the point wrappers (see [`PointWrapper`]). - fn verify( - &self, - ios: impl AsRef<[VrfIo]>, - ad: impl AsRef<[u8]>, - proof: &Proof, - ) -> Result<(), Error>; -} - -impl Prover for Secret { - fn prove(&self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>) -> Proof { - let (t, input) = vrf_transcript_input::(self.public.0, ios, ad); + pub fn prove(ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, secret: &Secret) -> Self { + let (t, input) = vrf_transcript_input::(secret.public.0, ios, ad); // Nonce - let mut k = S::nonce(&self.scalar, t.clone()); + let mut k = S::nonce(&secret.scalar, t.clone()); // R = k * I_m (secret nonce on merged input) let r = smul!(input.0, k).into_affine(); @@ -156,25 +113,53 @@ impl Prover for Secret { let c = S::challenge(&[&r], t); // Response - let mut cx = c * self.scalar; + let mut cx = c * secret.scalar; let s = k + cx; k.zeroize(); cx.zeroize(); Proof { r, s } } -} -impl Verifier for Public { - fn verify( + /// Verify the proof for the given VRF I/O pairs, additional data and + /// public key. + /// + /// Verifies that each VRF output is correctly derived from its input using + /// the secret key of `public`. Multiple I/O pairs are delinearized into a + /// single merged pair before verifying. + /// + /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the + /// public key or any I/O pair point is the group identity, + /// `Err(Error::VerificationFailure)` otherwise. + /// + /// All curve points involved in verification (public key, I/O pairs, and proof + /// points) are assumed to be in the prime-order subgroup. This is guaranteed + /// when points are constructed through checked constructors ([`Public::from_affine`], + /// [`Input::from_affine`], [`Output::from_affine`]), checked deserialization + /// (see [`PointWrapper`]) or through trusted operations like [`Input::new`] + /// (hash-to-curve) and [`Secret::vrf_io`]. Proof points are guaranteed valid + /// when deserialized via [`CanonicalDeserialize`] (which includes subgroup + /// checks) or produced by [`Proof::prove`]. Subgroup membership is not + /// re-checked here. + /// + /// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places + /// the burden of subgroup validation on the caller. Passing points with + /// cofactor components leads to undefined verification behavior. + /// + /// The group identity is checked unconditionally, for the public key and for + /// every I/O pair. Neither binds the proof to a signer: the secret scalar of + /// the identity key is publicly known, and a pair holding the identity is + /// satisfied by every secret key. It stays a legal value for the nonce + /// commitment `R`, which commits to nothing. + pub fn verify( &self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, - proof: &Proof, + public: &Public, ) -> Result<(), Error> { // With Y = 0 the challenge term drops out of the equation below and // anyone can pick s and set R = s * G. - if self.is_identity() { + if public.is_identity() { return Err(Error::InvalidData); } @@ -185,14 +170,14 @@ impl Verifier for Public { return Err(Error::InvalidData); } - let Proof { r, s } = proof; - let (t, zs) = vrf_transcript_scalars::(self.0, ios, ad); + let Proof { r, s } = self; + let (t, zs) = vrf_transcript_scalars::(public.0, ios, ad); // Challenge let c = S::challenge(&[r], t); // Verification: s * I_m - c * O_m == R - let lhs = utils::schnorr_lhs::(self.0, ios, &zs, *s, c); + let lhs = utils::schnorr_lhs::(public.0, ios, &zs, *s, c); if lhs != r.into_group() { return Err(Error::VerificationFailure); } @@ -201,6 +186,25 @@ impl Verifier for Public { } } +impl Secret { + /// Generate a Thin VRF proof. Same as [`Proof::prove`]. + pub fn prove_thin(&self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>) -> Proof { + Proof::prove(ios, ad, self) + } +} + +impl Public { + /// Verify a Thin VRF proof. Same as [`Proof::verify`]. + pub fn verify_thin( + &self, + ios: impl AsRef<[VrfIo]>, + ad: impl AsRef<[u8]>, + proof: &Proof, + ) -> Result<(), Error> { + proof.verify(ios, ad, self) + } +} + /// Deferred Thin VRF verification data for batch verification. /// /// Stores raw points and delinearization scalars instead of the merged pair, @@ -223,10 +227,10 @@ impl BatchItem { /// equation in [`BatchVerifier::verify`]. This is cheap and can be done in /// parallel. pub fn new( - public: &Public, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, proof: &Proof, + public: &Public, ) -> Self { let ios = ios.as_ref(); let (t, zs) = vrf_transcript_scalars::(public.0, ios, ad); @@ -247,7 +251,7 @@ impl BatchItem { /// Collects multiple proofs and verifies them together via a single /// multi-scalar multiplication. /// -/// The same subgroup membership assumptions as [`Verifier`] apply to all +/// The same subgroup membership assumptions as [`Proof::verify`] apply to all /// points fed into the batch (public keys, I/O pairs, and proof points). pub struct BatchVerifier { items: Vec>, @@ -273,12 +277,12 @@ impl BatchVerifier { /// Prepare and push a proof in one step. pub fn push( &mut self, - public: &Public, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, proof: &Proof, + public: &Public, ) { - self.push_prepared(BatchItem::new(public, ios, ad, proof)); + self.push_prepared(BatchItem::new(ios, ad, proof, public)); } /// Batch-verify all collected proofs using a single multi-scalar multiplication. @@ -377,43 +381,41 @@ pub(crate) mod testing { use crate::testing::{self as common, SuiteExt, TEST_SEED, random_val}; pub fn prove_verify() { - use thin::{Prover, Verifier}; - let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); let input = Input::from_affine_unchecked(random_val(None)); let io = secret.vrf_io(input); - let proof = secret.prove(io, b"foo"); - let result = public.verify(io, b"foo", &proof); + let proof = secret.prove_thin(io, b"foo"); + let result = public.verify_thin(io, b"foo", &proof); assert!(result.is_ok()); } pub fn batch_verify() { - use thin::{BatchItem, BatchVerifier, Prover, Verifier}; + use thin::{BatchItem, BatchVerifier}; let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); let input = Input::from_affine_unchecked(random_val(None)); let io = secret.vrf_io(input); - let proof1 = secret.prove(io, b"foo"); - let proof2 = secret.prove(io, b"bar"); + let proof1 = secret.prove_thin(io, b"foo"); + let proof2 = secret.prove_thin(io, b"bar"); // Single-proof verification still works. - assert!(public.verify(io, b"foo", &proof1).is_ok()); - assert!(public.verify(io, b"bar", &proof2).is_ok()); + assert!(public.verify_thin(io, b"foo", &proof1).is_ok()); + assert!(public.verify_thin(io, b"bar", &proof2).is_ok()); // Batch using push. let mut batch = BatchVerifier::new(); - batch.push(&public, io, b"foo", &proof1); - batch.push(&public, io, b"bar", &proof2); + batch.push(io, b"foo", &proof1, &public); + batch.push(io, b"bar", &proof2, &public); assert!(batch.verify().is_ok()); // Batch using BatchItem::new + push_prepared. let mut batch = BatchVerifier::new(); - let entry1 = BatchItem::new(&public, io, b"foo", &proof1); - let entry2 = BatchItem::new(&public, io, b"bar", &proof2); + let entry1 = BatchItem::new(io, b"foo", &proof1, &public); + let entry2 = BatchItem::new(io, b"bar", &proof2, &public); batch.push_prepared(entry1); batch.push_prepared(entry2); assert!(batch.verify().is_ok()); @@ -424,22 +426,20 @@ pub(crate) mod testing { // Bad additional data should fail. let mut batch = BatchVerifier::new(); - batch.push(&public, io, b"foo", &proof1); - batch.push(&public, io, b"wrong", &proof2); + batch.push(io, b"foo", &proof1, &public); + batch.push(io, b"wrong", &proof2, &public); assert!(batch.verify().is_err()); } /// N=1 slice produces same proof as passing a single `VrfIo`. pub fn prove_verify_multi_single() { - use thin::{Prover, Verifier}; - let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); let input = Input::from_affine_unchecked(random_val(None)); let io = secret.vrf_io(input); - let proof_single = secret.prove(io, b"foo"); - let proof_slice = secret.prove([io], b"foo"); + let proof_single = secret.prove_thin(io, b"foo"); + let proof_slice = secret.prove_thin([io], b"foo"); // Byte-identical proofs let encode = |p: &thin::Proof| { @@ -450,8 +450,8 @@ pub(crate) mod testing { assert_eq!(encode(&proof_single), encode(&proof_slice)); // Cross-verification - assert!(public.verify(io, b"foo", &proof_slice).is_ok()); - assert!(public.verify([io], b"foo", &proof_single).is_ok()); + assert!(public.verify_thin(io, b"foo", &proof_slice).is_ok()); + assert!(public.verify_thin([io], b"foo", &proof_single).is_ok()); } /// An identity public key must be rejected by both verifiers. @@ -462,7 +462,7 @@ pub(crate) mod testing { /// `Public` to make sure the rejection does not depend on the key having gone /// through a checked constructor. pub fn identity_public_key_rejected() { - use thin::{BatchVerifier, Verifier}; + use thin::BatchVerifier; let identity = Public::::from_affine_unchecked(AffinePoint::::zero()); let s = ScalarField::::from(0x5eed_u64); @@ -471,10 +471,10 @@ pub(crate) mod testing { s, }; - assert!(identity.verify([], b"forgery", &forged).is_err()); + assert!(identity.verify_thin([], b"forgery", &forged).is_err()); let mut batch = BatchVerifier::new(); - batch.push(&identity, [], b"forgery", &forged); + batch.push([], b"forgery", &forged, &identity); assert!(batch.verify().is_err()); } @@ -487,7 +487,7 @@ pub(crate) mod testing { /// hides the bad pair behind a good one, where the merged pair alone is not /// enough to catch it. pub fn identity_io_pair_rejected() { - use thin::{BatchVerifier, Prover, Verifier}; + use thin::BatchVerifier; let identity_io = VrfIo:: { input: Input::from_affine_unchecked(AffinePoint::::zero()), @@ -498,28 +498,30 @@ pub(crate) mod testing { let secret = Secret::::from_seed(seed); let public = secret.public(); - let proof = secret.prove([identity_io], b"forgery"); - assert!(public.verify([identity_io], b"forgery", &proof).is_err()); + let proof = secret.prove_thin([identity_io], b"forgery"); + assert!( + public + .verify_thin([identity_io], b"forgery", &proof) + .is_err() + ); let mut batch = BatchVerifier::new(); - batch.push(&public, [identity_io], b"forgery", &proof); + batch.push([identity_io], b"forgery", &proof, &public); assert!(batch.verify().is_err()); let good_io = secret.vrf_io(Input::new(b"good").unwrap()); let ios = [good_io, identity_io]; - let proof = secret.prove(ios, b"forgery"); - assert!(public.verify(ios, b"forgery", &proof).is_err()); + let proof = secret.prove_thin(ios, b"forgery"); + assert!(public.verify_thin(ios, b"forgery", &proof).is_err()); let mut batch = BatchVerifier::new(); - batch.push(&public, ios, b"forgery", &proof); + batch.push(ios, b"forgery", &proof, &public); assert!(batch.verify().is_err()); } } /// N=3 VRF pairs: verify succeeds; tampered output/input/ad fails. pub fn prove_verify_multi() { - use thin::{Prover, Verifier}; - let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); @@ -530,53 +532,50 @@ pub(crate) mod testing { }) .collect(); - let proof = secret.prove(&ios[..], b"bar"); - assert!(public.verify(&ios[..], b"bar", &proof).is_ok()); + let proof = secret.prove_thin(&ios[..], b"bar"); + assert!(public.verify_thin(&ios[..], b"bar", &proof).is_ok()); // Tamper: wrong output on ios[1] let mut bad_ios = ios.clone(); bad_ios[1].output = secret.output(ios[0].input); - assert!(public.verify(&bad_ios[..], b"bar", &proof).is_err()); + assert!(public.verify_thin(&bad_ios[..], b"bar", &proof).is_err()); // Tamper: wrong input on ios[0] let mut bad_ios = ios.clone(); bad_ios[0].input = ios[1].input; - assert!(public.verify(&bad_ios[..], b"bar", &proof).is_err()); + assert!(public.verify_thin(&bad_ios[..], b"bar", &proof).is_err()); // Tamper: wrong ad - assert!(public.verify(&ios[..], b"baz", &proof).is_err()); + assert!(public.verify_thin(&ios[..], b"baz", &proof).is_err()); } /// N=0 VRF pairs degenerates to Schnorr signature over ad. pub fn prove_verify_multi_empty() { - use thin::{Prover, Verifier}; - let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); - let proof = secret.prove([], b"bar"); - assert!(public.verify([], b"bar", &proof).is_ok()); + let proof = secret.prove_thin([], b"bar"); + assert!(public.verify_thin([], b"bar", &proof).is_ok()); // Wrong ad should fail - assert!(public.verify([], b"baz", &proof).is_err()); + assert!(public.verify_thin([], b"baz", &proof).is_err()); } /// `R` is the identity only for a zero nonce, so that proof is built by /// hand. One proof must have one encoding. pub fn proof_encoding_is_canonical() { use ark_serialize::Compress; - use thin::{Prover, Verifier}; let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); let input = Input::from_affine_unchecked(random_val(None)); let io = secret.vrf_io(input); - let proof = secret.prove(io, b"foo"); + let proof = secret.prove_thin(io, b"foo"); let mut bytes = Vec::new(); proof.serialize_compressed(&mut bytes).unwrap(); let decoded = Proof::::deserialize_compressed(&bytes[..]).unwrap(); - assert!(public.verify(io, b"foo", &decoded).is_ok()); + assert!(public.verify_thin(io, b"foo", &decoded).is_ok()); let mut reencoded = Vec::new(); decoded.serialize_compressed(&mut reencoded).unwrap(); assert_eq!(bytes, reencoded); @@ -606,7 +605,7 @@ pub(crate) mod testing { /// is the independent check that the prover merged correctly. pub fn prove_verify_multi_msm() { use crate::utils::common::MSM_THRESHOLD; - use thin::{BatchVerifier, Prover, Verifier}; + use thin::BatchVerifier; let secret = Secret::::from_seed(TEST_SEED); let public = secret.public(); @@ -614,18 +613,18 @@ pub(crate) mod testing { .map(|i| secret.vrf_io(Input::new(&[i]).unwrap())) .collect(); - let proof = secret.prove(&ios[..], b"msm"); - assert!(public.verify(&ios[..], b"msm", &proof).is_ok()); + let proof = secret.prove_thin(&ios[..], b"msm"); + assert!(public.verify_thin(&ios[..], b"msm", &proof).is_ok()); let mut batch = BatchVerifier::new(); - batch.push(&public, &ios[..], b"msm", &proof); + batch.push(&ios[..], b"msm", &proof, &public); assert!(batch.verify().is_ok()); // Tamper: wrong output on the last pair let mut bad_ios = ios.clone(); bad_ios[MSM_THRESHOLD - 1].output = ios[0].output; - assert!(public.verify(&bad_ios[..], b"msm", &proof).is_err()); + assert!(public.verify_thin(&bad_ios[..], b"msm", &proof).is_err()); let mut batch = BatchVerifier::new(); - batch.push(&public, &bad_ios[..], b"msm", &proof); + batch.push(&bad_ios[..], b"msm", &proof, &public); assert!(batch.verify().is_err()); } @@ -712,14 +711,13 @@ pub(crate) mod testing { } fn new(comment: &str, seed: &[u8; 32], alpha: &[u8], ad: &[u8]) -> Self { - use super::Prover; let base = common::TestVector::new(comment, seed, alpha, ad); let io = VrfIo { input: Input::::from_affine_unchecked(base.h), output: Output::from_affine_unchecked(base.gamma), }; let secret = Secret::from_scalar(base.sk); - let proof: Proof = secret.prove(io, ad); + let proof: Proof = secret.prove_thin(io, ad); Self { base, proof_r: proof.r, @@ -763,12 +761,12 @@ pub(crate) mod testing { output: Output::from_affine_unchecked(self.base.gamma), }; let sk = Secret::from_scalar(self.base.sk); - let proof = sk.prove(io, &self.base.ad); + let proof = sk.prove_thin(io, &self.base.ad); assert_eq!(self.proof_r, proof.r, "Thin VRF proof R mismatch"); assert_eq!(self.proof_s, proof.s, "Thin VRF proof s mismatch"); let pk = Public::::from_affine_unchecked(self.base.pk); - assert!(pk.verify(io, &self.base.ad, &proof).is_ok()); + assert!(pk.verify_thin(io, &self.base.ad, &proof).is_ok()); } } @@ -851,7 +849,7 @@ pub(crate) mod testing { // = RHS let public = Public::::from_affine_unchecked(pk); assert!( - public.verify(fake_io, ad, &forged_proof).is_ok(), + public.verify_thin(fake_io, ad, &forged_proof).is_ok(), "Forged proof must verify when input discrete log is known" ); } diff --git a/src/tiny.rs b/src/tiny.rs index 598d687..e977673 100644 --- a/src/tiny.rs +++ b/src/tiny.rs @@ -9,7 +9,7 @@ //! //! ```rust,ignore //! use ark_vrf::suites::bandersnatch::*; -//! use ark_vrf::tiny::{Prover, Verifier}; +//! use ark_vrf::tiny::Proof; //! //! let secret = Secret::from_seed([0; 32]); //! let public = secret.public(); @@ -17,10 +17,14 @@ //! let io = secret.vrf_io(input); //! //! // Proving -//! let proof = secret.prove(io, b"aux data"); +//! let proof = Proof::prove(io, b"aux data", &secret); //! //! // Verification -//! let result = public.verify(io, b"aux data", &proof); +//! let result = proof.verify(io, b"aux data", &public); +//! +//! // The same, as methods of the keys +//! let proof = secret.prove_tiny(io, b"aux data"); +//! let result = public.verify_tiny(io, b"aux data", &proof); //! ``` use super::*; @@ -57,7 +61,7 @@ fn vrf_transcript_scalars( /// - `c`: Challenge scalar /// - `s`: Response scalar (`s = k + c * x`) /// -/// Construct it with [`Prover::prove`] or by deserialization. Serialization +/// Construct it with [`Proof::prove`] or by deserialization. Serialization /// encodes `c` on [`Suite::CHALLENGE_LEN`] bytes and `s` as a full scalar, and /// accepts one encoding per `c`. The proof holds no curve points, so /// deserialization involves no subgroup checks. @@ -138,81 +142,62 @@ impl ark_serialize::Valid for Proof { } } -/// Trait for types that can generate Tiny VRF proofs. -pub trait Prover { +impl Proof { /// Generate a proof for the given VRF I/O pairs and additional data. /// /// Multiple I/O pairs are delinearized into a single merged pair before proving. - fn prove(&self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>) -> Proof; -} - -/// Trait for types that can verify Tiny VRF proofs. -/// -/// Verifies that a VRF output is correctly derived from an input using the -/// secret key of the given public key. -/// -/// All curve points involved in verification (public key and I/O pairs) -/// are assumed to be in the prime-order subgroup. This is guaranteed -/// when points are constructed through checked constructors ([`Public::from_affine`], -/// [`Input::from_affine`], [`Output::from_affine`]) or through trusted -/// operations like [`Input::new`] (hash-to-curve) and [`Secret::vrf_io`]. -/// -/// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places -/// the burden of subgroup validation on the caller. Passing points with -/// cofactor components leads to undefined verification behavior. -/// -/// The group identity is checked unconditionally, for the public key and for -/// every I/O pair. Neither binds the proof to a signer: the secret scalar of -/// the identity key is publicly known, and a pair holding the identity is -/// satisfied by every secret key. -pub trait Verifier { - /// Verify a proof for the given VRF I/O pairs and additional data. - /// - /// Multiple I/O pairs are delinearized into a single merged pair before verifying. - /// - /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the - /// public key or any I/O pair point is the group identity, - /// `Err(Error::VerificationFailure)` otherwise. - /// - /// Subgroup membership of the points is not re-checked here. It is - /// guaranteed by the checked constructors and checked deserialization of - /// the point wrappers (see [`PointWrapper`]). - fn verify( - &self, - ios: impl AsRef<[VrfIo]>, - ad: impl AsRef<[u8]>, - proof: &Proof, - ) -> Result<(), Error>; -} + pub fn prove(ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, secret: &Secret) -> Self { + let (t, input) = vrf_transcript_input::(secret.public.0, ios, ad); -impl Prover for Secret { - fn prove(&self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>) -> Proof { - let (t, input) = vrf_transcript_input::(self.public.0, ios, ad); - - let mut k = S::nonce(&self.scalar, t.clone()); + let mut k = S::nonce(&secret.scalar, t.clone()); // R = k * I_m let r = smul!(input.0, k).into_affine(); let c = S::challenge(&[&r], t); - let mut cx = c * self.scalar; + let mut cx = c * secret.scalar; let s = k + cx; k.zeroize(); cx.zeroize(); Proof { c, s } } -} -impl Verifier for Public { - fn verify( + /// Verify the proof for the given VRF I/O pairs, additional data and + /// public key. + /// + /// Verifies that each VRF output is correctly derived from its input using + /// the secret key of `public`. Multiple I/O pairs are delinearized into a + /// single merged pair before verifying. + /// + /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the + /// public key or any I/O pair point is the group identity, + /// `Err(Error::VerificationFailure)` otherwise. + /// + /// All curve points involved in verification (public key and I/O pairs) + /// are assumed to be in the prime-order subgroup. This is guaranteed + /// when points are constructed through checked constructors ([`Public::from_affine`], + /// [`Input::from_affine`], [`Output::from_affine`]), checked deserialization + /// (see [`PointWrapper`]) or through trusted operations like [`Input::new`] + /// (hash-to-curve) and [`Secret::vrf_io`]. Subgroup membership is not + /// re-checked here. + /// + /// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places + /// the burden of subgroup validation on the caller. Passing points with + /// cofactor components leads to undefined verification behavior. + /// + /// The group identity is checked unconditionally, for the public key and for + /// every I/O pair. Neither binds the proof to a signer: the secret scalar of + /// the identity key is publicly known, and a pair holding the identity is + /// satisfied by every secret key. + pub fn verify( &self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>, - proof: &Proof, + public: &Public, ) -> Result<(), Error> { // With Y = 0 the challenge term drops out of the equation below and // anyone can produce a matching (c, s) pair. - if self.is_identity() { + if public.is_identity() { return Err(Error::InvalidData); } @@ -223,12 +208,12 @@ impl Verifier for Public { return Err(Error::InvalidData); } - let (t, zs) = vrf_transcript_scalars::(self.0, ios, ad); + let (t, zs) = vrf_transcript_scalars::(public.0, ios, ad); - let Proof { c, s } = proof; + let Proof { c, s } = self; // R = s * I_m - c * O_m - let r = utils::schnorr_lhs::(self.0, ios, &zs, *s, *c).into_affine(); + let r = utils::schnorr_lhs::(public.0, ios, &zs, *s, *c).into_affine(); let c_exp = S::challenge(&[&r], t); (c_exp == *c) @@ -237,6 +222,25 @@ impl Verifier for Public { } } +impl Secret { + /// Generate a Tiny VRF proof. Same as [`Proof::prove`]. + pub fn prove_tiny(&self, ios: impl AsRef<[VrfIo]>, ad: impl AsRef<[u8]>) -> Proof { + Proof::prove(ios, ad, self) + } +} + +impl Public { + /// Verify a Tiny VRF proof. Same as [`Proof::verify`]. + pub fn verify_tiny( + &self, + ios: impl AsRef<[VrfIo]>, + ad: impl AsRef<[u8]>, + proof: &Proof, + ) -> Result<(), Error> { + proof.verify(ios, ad, self) + } +} + #[cfg(test)] pub mod testing { use super::*; @@ -248,8 +252,8 @@ pub mod testing { let input = Input::from_affine_unchecked(common::random_val(None)); let io = secret.vrf_io(input); - let proof = secret.prove(io, b"foo"); - let result = public.verify(io, b"foo", &proof); + let proof = secret.prove_tiny(io, b"foo"); + let result = public.verify_tiny(io, b"foo", &proof); assert!(result.is_ok()); } @@ -258,12 +262,12 @@ pub mod testing { let public = secret.public(); let ios: [VrfIo; 0] = []; - let proof = secret.prove(ios, b"bar"); + let proof = secret.prove_tiny(ios, b"bar"); - assert!(public.verify(ios, b"bar", &proof).is_ok()); + assert!(public.verify_tiny(ios, b"bar", &proof).is_ok()); // Wrong ad should fail - assert!(public.verify(ios, b"baz", &proof).is_err()); + assert!(public.verify_tiny(ios, b"baz", &proof).is_err()); } /// N=1 slice produces same proof as passing a single `VrfIo`. @@ -273,8 +277,8 @@ pub mod testing { let input = Input::from_affine_unchecked(common::random_val(None)); let io = secret.vrf_io(input); - let proof_single = secret.prove(io, b"foo"); - let proof_slice = secret.prove([io], b"foo"); + let proof_single = secret.prove_tiny(io, b"foo"); + let proof_slice = secret.prove_tiny([io], b"foo"); // Byte-identical proofs let encode = |p: &tiny::Proof| { @@ -285,8 +289,8 @@ pub mod testing { assert_eq!(encode(&proof_single), encode(&proof_slice)); // Cross-verification - assert!(public.verify(io, b"foo", &proof_slice).is_ok()); - assert!(public.verify([io], b"foo", &proof_single).is_ok()); + assert!(public.verify_tiny(io, b"foo", &proof_slice).is_ok()); + assert!(public.verify_tiny([io], b"foo", &proof_single).is_ok()); } /// An identity public key must be rejected by the verifier. @@ -299,8 +303,8 @@ pub mod testing { let identity = Public::::from_affine_unchecked(AffinePoint::::zero()); let zero_secret = Secret::::from_scalar(ScalarField::::zero()); - let proof = zero_secret.prove([], b"forgery"); - assert!(identity.verify([], b"forgery", &proof).is_err()); + let proof = zero_secret.prove_tiny([], b"forgery"); + assert!(identity.verify_tiny([], b"forgery", &proof).is_err()); } /// An I/O pair holding the identity must be rejected by the verifier. @@ -321,13 +325,17 @@ pub mod testing { let secret = Secret::::from_seed(seed); let public = secret.public(); - let proof = secret.prove([identity_io], b"forgery"); - assert!(public.verify([identity_io], b"forgery", &proof).is_err()); + let proof = secret.prove_tiny([identity_io], b"forgery"); + assert!( + public + .verify_tiny([identity_io], b"forgery", &proof) + .is_err() + ); let good_io = secret.vrf_io(Input::new(b"good").unwrap()); let ios = [good_io, identity_io]; - let proof = secret.prove(ios, b"forgery"); - assert!(public.verify(ios, b"forgery", &proof).is_err()); + let proof = secret.prove_tiny(ios, b"forgery"); + assert!(public.verify_tiny(ios, b"forgery", &proof).is_err()); } } @@ -347,21 +355,21 @@ pub mod testing { output: Output::from_affine_unchecked(public.0), }); - let proof = secret.prove(&ios[..], b"bar"); - assert!(public.verify(&ios[..], b"bar", &proof).is_ok()); + let proof = secret.prove_tiny(&ios[..], b"bar"); + assert!(public.verify_tiny(&ios[..], b"bar", &proof).is_ok()); // Tamper: wrong output on ios[1] let mut bad_ios = ios.clone(); bad_ios[1].output = secret.output(ios[0].input); - assert!(public.verify(&bad_ios[..], b"bar", &proof).is_err()); + assert!(public.verify_tiny(&bad_ios[..], b"bar", &proof).is_err()); // Tamper: wrong input on ios[0] let mut bad_ios = ios.clone(); bad_ios[0].input = ios[1].input; - assert!(public.verify(&bad_ios[..], b"bar", &proof).is_err()); + assert!(public.verify_tiny(&bad_ios[..], b"bar", &proof).is_err()); // Tamper: wrong ad - assert!(public.verify(&ios[..], b"baz", &proof).is_err()); + assert!(public.verify_tiny(&ios[..], b"baz", &proof).is_err()); } /// `merge_ios` switches to its MSM branch at `MSM_THRESHOLD` pairs. This @@ -376,13 +384,13 @@ pub mod testing { .map(|i| secret.vrf_io(Input::new(&[i]).unwrap())) .collect(); - let proof = secret.prove(&ios[..], b"msm"); - assert!(public.verify(&ios[..], b"msm", &proof).is_ok()); + let proof = secret.prove_tiny(&ios[..], b"msm"); + assert!(public.verify_tiny(&ios[..], b"msm", &proof).is_ok()); // Tamper: wrong output on the last pair let mut bad_ios = ios.clone(); bad_ios[MSM_THRESHOLD - 1].output = ios[0].output; - assert!(public.verify(&bad_ios[..], b"msm", &proof).is_err()); + assert!(public.verify_tiny(&bad_ios[..], b"msm", &proof).is_err()); } #[macro_export] @@ -458,14 +466,13 @@ pub mod testing { } fn new(comment: &str, seed: &[u8; 32], alpha: &[u8], ad: &[u8]) -> Self { - use super::Prover; let base = common::TestVector::new(comment, seed, alpha, ad); let io = VrfIo { input: Input::from_affine_unchecked(base.h), output: Output::from_affine_unchecked(base.gamma), }; let sk = Secret::from_scalar(base.sk); - let proof: Proof = sk.prove(io, ad); + let proof: Proof = sk.prove_tiny(io, ad); Self { base, c: proof.c, @@ -501,12 +508,12 @@ pub mod testing { output: Output::from_affine_unchecked(self.base.gamma), }; let sk = Secret::from_scalar(self.base.sk); - let proof = sk.prove(io, &self.base.ad); + let proof = sk.prove_tiny(io, &self.base.ad); assert_eq!(self.c, proof.c, "VRF proof challenge ('c') mismatch"); assert_eq!(self.s, proof.s, "VRF proof response ('s') mismatch"); let pk = Public::::from_affine_unchecked(self.base.pk); - assert!(pk.verify(io, &self.base.ad, &proof).is_ok()); + assert!(pk.verify_tiny(io, &self.base.ad, &proof).is_ok()); } } @@ -520,7 +527,7 @@ pub mod testing { let secret = Secret::::from_seed(common::TEST_SEED); let public = secret.public(); let io = secret.vrf_io(Input::new(b"wide").unwrap()); - let proof = secret.prove(io, b"ad"); + let proof = secret.prove_tiny(io, b"ad"); let c_bytes = common::scalar_encode::(&proof.c); assert!(c_bytes[16..].iter().any(|byte| *byte != 0)); @@ -529,7 +536,7 @@ pub mod testing { assert_eq!(bytes.len(), 64); assert_eq!(bytes.len(), proof.compressed_size()); let decoded = Proof::::deserialize_compressed(&bytes[..]).unwrap(); - assert!(public.verify(io, b"ad", &decoded).is_ok()); + assert!(public.verify_tiny(io, b"ad", &decoded).is_ok()); } /// `Suite::CHALLENGE_LEN` may exceed the level, up to the scalar width. @@ -545,7 +552,7 @@ pub mod testing { let secret = Secret::::from_seed(common::TEST_SEED); let public = secret.public(); let io = secret.vrf_io(Input::new(b"wide").unwrap()); - let proof = secret.prove(io, b"ad"); + let proof = secret.prove_tiny(io, b"ad"); let c_bytes = common::scalar_encode::(&proof.c); assert!(c_bytes[16..].iter().any(|byte| *byte != 0)); @@ -553,7 +560,7 @@ pub mod testing { proof.serialize_compressed(&mut bytes).unwrap(); assert_eq!(bytes.len(), 64); let decoded = Proof::::deserialize_compressed(&bytes[..]).unwrap(); - assert!(public.verify(io, b"ad", &decoded).is_ok()); + assert!(public.verify_tiny(io, b"ad", &decoded).is_ok()); let mut alias = proof.c.into_bigint(); assert!(!alias.add_with_carry(&ScalarField::::MODULUS)); diff --git a/src/utils/transcript.rs b/src/utils/transcript.rs index d718558..6f6c363 100644 --- a/src/utils/transcript.rs +++ b/src/utils/transcript.rs @@ -1,6 +1,6 @@ //! Fiat-Shamir transcripts. -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_serialize::CanonicalSerialize; use ark_std::io; use digest::Digest; use digest::generic_array::GenericArray; @@ -52,14 +52,6 @@ pub trait Transcript: Clone + io::Read + io::Write { obj.serialize_compressed(self).unwrap(); } - /// Squeeze and deserialize an object from the transcript. - /// - /// Reads bytes from the squeeze_raw stream via the [`io::Read`] - /// implementation and deserializes them directly. - fn squeeze_deserialize(&mut self) -> T { - T::deserialize_compressed(self).unwrap() - } - /// Consume the transcript and return an RNG that draws from the squeeze stream. fn to_rng(self) -> TranscriptRng where @@ -92,8 +84,6 @@ impl ark_std::rand::RngCore for TranscriptRng { } } -impl ark_std::rand::CryptoRng for TranscriptRng {} - // --------------------------------------------------------------------------- // XofTranscript: single transcript implementation for all XOF-like hashers // ---------------------------------------------------------------------------