ark_vrf/
pedersen.rs

1//! # Pedersen-VRF
2//!
3//! Implementation of a key-hiding VRF scheme using Pedersen commitments as described in
4//! [BCHSV23](https://eprint.iacr.org/2023/002).
5//!
6//! This scheme extends the IETF VRF by adding key privacy through blinding factors,
7//! allowing verification without revealing which specific public key was used.
8//!
9//! ## Usage Example
10//!
11//! ```rust,ignore
12//! // Key generation
13//! let secret = Secret::<MySuite>::from_seed(b"seed");
14//! let public = secret.public();
15//!
16//! // Proving
17//! use ark_vrf::pedersen::Prover;
18//! let input = Input::from(my_data);
19//! let output = secret.output(input);
20//! let (proof, blinding) = secret.prove(input, output, aux_data);
21//!
22//! // Verification
23//! use ark_vrf::pedersen::Verifier;
24//! let result = Public::verify(input, output, aux_data, &proof);
25//!
26//! // Verify the proof was created using a specific public key
27//! // This requires knowledge of the blinding factor
28//! let expected_commitment = (public.0 + MySuite::BLINDING_BASE * blinding).into_affine();
29//! assert_eq!(proof.key_commitment(), expected_commitment);
30//! ```
31
32use crate::ietf::IetfSuite;
33use crate::*;
34
35/// Magic spell for [`PedersenSuite::BLINDING_BASE`] generation in built-in implementations.
36///
37/// (en) *"the blinding foundation of hidden light which eludes the mind and creates darkness for those who see"*
38pub const PEDERSEN_BASE_SEED: &[u8] =
39    b"basis caecans lucis occultae quae mentem fugit et tenebras iis qui vident creat";
40
41pub trait PedersenSuite: IetfSuite {
42    /// Blinding base.
43    const BLINDING_BASE: AffinePoint<Self>;
44
45    /// Pedersen blinding factor.
46    ///
47    /// Default implementation is deterministic and loosely inspired by the RFC-9381
48    /// challenge procedure. All parameters but `secret` are public.
49    fn blinding(
50        secret: &ScalarField<Self>,
51        input: &AffinePoint<Self>,
52        aux: &[u8],
53    ) -> ScalarField<Self> {
54        const DOM_SEP_START: u8 = 0xCC;
55        const DOM_SEP_END: u8 = 0x00;
56        let mut buf = [Self::SUITE_ID, &[DOM_SEP_START]].concat();
57        Self::Codec::scalar_encode_into(secret, &mut buf);
58        Self::Codec::point_encode_into(input, &mut buf);
59        buf.extend_from_slice(aux);
60        buf.push(DOM_SEP_END);
61        let hash = &utils::hash::<Self::Hasher>(&buf);
62        ScalarField::<Self>::from_be_bytes_mod_order(hash)
63    }
64}
65
66/// Pedersen VRF proof.
67///
68/// Zero-knowledge proof with key-hiding properties:
69/// - `pk_com`: Commitment to the public key (Y_b = x·G + b·B)
70/// - `r`: Nonce commitment for the generator (R = k·G + k_b·B)
71/// - `ok`: Nonce commitment for the input point (O_k = k·I)
72/// - `s`: Response scalar for the secret key
73/// - `sb`: Response scalar for the blinding factor
74#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)]
75pub struct Proof<S: PedersenSuite> {
76    pk_com: AffinePoint<S>,
77    r: AffinePoint<S>,
78    ok: AffinePoint<S>,
79    s: ScalarField<S>,
80    sb: ScalarField<S>,
81}
82
83impl<S: PedersenSuite> Proof<S> {
84    /// Get public key commitment from proof.
85    pub fn key_commitment(&self) -> AffinePoint<S> {
86        self.pk_com
87    }
88}
89
90/// Trait for types that can generate Pedersen VRF proofs.
91///
92/// Implementors can create zero-knowledge proofs that a VRF output
93/// is correctly derived from an input using their secret key,
94/// while hiding the specific public key used.
95pub trait Prover<S: PedersenSuite> {
96    /// Generate a proof for the given input/output and additional data.
97    ///
98    /// Creates a zero-knowledge proof binding the input, output, and additional data
99    /// to a commitment of the prover's public key rather than the key itself.
100    ///
101    /// * `input` - VRF input point
102    /// * `output` - VRF output point (γ = x·H)
103    /// * `ad` - Additional data to bind to the proof
104    ///
105    /// Returns the proof together with the associated blinding factor.
106    fn prove(
107        &self,
108        input: Input<S>,
109        output: Output<S>,
110        ad: impl AsRef<[u8]>,
111    ) -> (Proof<S>, ScalarField<S>);
112}
113
114/// Trait for entities that can verify Pedersen VRF proofs.
115///
116/// Implementors can verify that a VRF output is correctly derived
117/// from an input using a committed public key.
118pub trait Verifier<S: PedersenSuite> {
119    /// Verify a proof for the given input/output and additional data.
120    ///
121    /// Verifies the cryptographic relationship between input, output, and proof
122    /// without requiring knowledge of which specific public key was used.
123    /// Confirms that the secret key used to generate the output is the same as
124    /// the one committed to in the proof.
125    ///
126    /// * `input` - VRF input point
127    /// * `output` - Claimed VRF output point
128    /// * `ad` - Additional data bound to the proof
129    /// * `proof` - The proof to verify
130    ///
131    /// Returns `Ok(())` if verification succeeds, `Err(Error::VerificationFailure)` otherwise.
132    fn verify(
133        input: Input<S>,
134        output: Output<S>,
135        ad: impl AsRef<[u8]>,
136        proof: &Proof<S>,
137    ) -> Result<(), Error>;
138}
139
140impl<S: PedersenSuite> Prover<S> for Secret<S> {
141    fn prove(
142        &self,
143        input: Input<S>,
144        output: Output<S>,
145        ad: impl AsRef<[u8]>,
146    ) -> (Proof<S>, ScalarField<S>) {
147        // Build blinding factor
148        let blinding = S::blinding(&self.scalar, &input.0, ad.as_ref());
149
150        // Construct the nonces
151        let k = S::nonce(&self.scalar, input);
152        let kb = S::nonce(&blinding, input);
153
154        // Yb = x*G + b*B
155        let xg = smul!(S::generator(), self.scalar);
156        let bb = smul!(S::BLINDING_BASE, blinding);
157        let pk_com = (xg + bb).into_affine();
158
159        // R = k*G + kb*B
160        let kg = smul!(S::generator(), k);
161        let kbb = smul!(S::BLINDING_BASE, kb);
162        let r = (kg + kbb).into_affine();
163
164        // Ok = k*I
165        let ok = smul!(input.0, k).into_affine();
166
167        // c = Hash(Yb, I, O, R, Ok, ad)
168        let c = S::challenge(&[&pk_com, &input.0, &output.0, &r, &ok], ad.as_ref());
169
170        // s = k + c*x
171        let s = k + c * self.scalar;
172        // sb = kb + c*b
173        let sb = kb + c * blinding;
174
175        let proof = Proof {
176            pk_com,
177            r,
178            ok,
179            s,
180            sb,
181        };
182        (proof, blinding)
183    }
184}
185
186impl<S: PedersenSuite> Verifier<S> for Public<S> {
187    fn verify(
188        input: Input<S>,
189        output: Output<S>,
190        ad: impl AsRef<[u8]>,
191        proof: &Proof<S>,
192    ) -> Result<(), Error> {
193        let Proof {
194            pk_com,
195            r,
196            ok,
197            s,
198            sb,
199        } = proof;
200
201        // c = Hash(Yb, I, O, R, Ok, ad)
202        let c = S::challenge(&[pk_com, &input.0, &output.0, r, ok], ad.as_ref());
203
204        // Ok + c*O = s*I
205        if output.0 * c + ok != input.0 * s {
206            return Err(Error::VerificationFailure);
207        }
208
209        // R + c*Yb = s*G + sb*B
210        if *pk_com * c + r != S::generator() * s + S::BLINDING_BASE * sb {
211            return Err(Error::VerificationFailure);
212        }
213
214        Ok(())
215    }
216}
217
218#[cfg(test)]
219pub(crate) mod testing {
220    use super::*;
221    use crate::testing::{self as common, CheckPoint, SuiteExt, TEST_SEED, random_val};
222
223    pub fn prove_verify<S: PedersenSuite>() {
224        use pedersen::{Prover, Verifier};
225
226        let secret = Secret::<S>::from_seed(TEST_SEED);
227        let input = Input::from(random_val(None));
228        let output = secret.output(input);
229
230        let (proof, blinding) = secret.prove(input, output, b"foo");
231        let result = Public::verify(input, output, b"foo", &proof);
232        assert!(result.is_ok());
233
234        assert_eq!(
235            proof.key_commitment(),
236            (secret.public().0 + S::BLINDING_BASE * blinding).into()
237        );
238    }
239
240    pub fn blinding_base_check<S: PedersenSuite>()
241    where
242        AffinePoint<S>: CheckPoint,
243    {
244        // Check that point has been computed using the magic spell.
245        assert_eq!(
246            S::BLINDING_BASE,
247            S::data_to_point(PEDERSEN_BASE_SEED).unwrap()
248        );
249        // Check that the point is on curve.
250        assert!(S::BLINDING_BASE.check(true).is_ok());
251    }
252
253    #[macro_export]
254    macro_rules! pedersen_suite_tests {
255        ($suite:ty) => {
256            mod pedersen {
257                use super::*;
258
259                #[test]
260                fn prove_verify() {
261                    $crate::pedersen::testing::prove_verify::<$suite>();
262                }
263
264                #[test]
265                fn blinding_base_check() {
266                    $crate::pedersen::testing::blinding_base_check::<$suite>();
267                }
268
269                $crate::test_vectors!($crate::pedersen::testing::TestVector<$suite>);
270            }
271        };
272    }
273
274    pub struct TestVector<S: PedersenSuite> {
275        pub base: common::TestVector<S>,
276        pub blind: ScalarField<S>,
277        pub proof: Proof<S>,
278    }
279
280    impl<S: PedersenSuite> core::fmt::Debug for TestVector<S> {
281        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
282            f.debug_struct("TestVector")
283                .field("base", &self.base)
284                .field("blinding", &self.blind)
285                .field("proof_pk_com", &self.proof.pk_com)
286                .field("proof_r", &self.proof.r)
287                .field("proof_ok", &self.proof.ok)
288                .field("proof_s", &self.proof.s)
289                .field("proof_sb", &self.proof.sb)
290                .finish()
291        }
292    }
293
294    impl<S> common::TestVectorTrait for TestVector<S>
295    where
296        S: PedersenSuite + SuiteExt + std::fmt::Debug,
297    {
298        fn name() -> String {
299            S::suite_name() + "_pedersen"
300        }
301
302        fn new(comment: &str, seed: &[u8], alpha: &[u8], salt: &[u8], ad: &[u8]) -> Self {
303            use super::Prover;
304            let base = common::TestVector::new(comment, seed, alpha, salt, ad);
305            let input = Input::<S>::from(base.h);
306            let output = Output::from(base.gamma);
307            let secret = Secret::from_scalar(base.sk);
308            let (proof, blind) = secret.prove(input, output, ad);
309            Self { base, blind, proof }
310        }
311
312        fn from_map(map: &common::TestVectorMap) -> Self {
313            let base = common::TestVector::from_map(map);
314            let blind = S::Codec::scalar_decode(&map.get_bytes("blinding"));
315            let pk_com = S::Codec::point_decode(&map.get_bytes("proof_pk_com")).unwrap();
316            let r = codec::point_decode::<S>(&map.get_bytes("proof_r")).unwrap();
317            let ok = codec::point_decode::<S>(&map.get_bytes("proof_ok")).unwrap();
318            let s = S::Codec::scalar_decode(&map.get_bytes("proof_s"));
319            let sb = S::Codec::scalar_decode(&map.get_bytes("proof_sb"));
320            let proof = Proof {
321                pk_com,
322                r,
323                ok,
324                s,
325                sb,
326            };
327            Self { base, blind, proof }
328        }
329
330        fn to_map(&self) -> common::TestVectorMap {
331            let items = [
332                (
333                    "blinding",
334                    hex::encode(codec::scalar_encode::<S>(&self.blind)),
335                ),
336                (
337                    "proof_pk_com",
338                    hex::encode(codec::point_encode::<S>(&self.proof.pk_com)),
339                ),
340                (
341                    "proof_r",
342                    hex::encode(codec::point_encode::<S>(&self.proof.r)),
343                ),
344                (
345                    "proof_ok",
346                    hex::encode(codec::point_encode::<S>(&self.proof.ok)),
347                ),
348                (
349                    "proof_s",
350                    hex::encode(codec::scalar_encode::<S>(&self.proof.s)),
351                ),
352                (
353                    "proof_sb",
354                    hex::encode(codec::scalar_encode::<S>(&self.proof.sb)),
355                ),
356            ];
357            let mut map = self.base.to_map();
358            items.into_iter().for_each(|(name, value)| {
359                map.0.insert(name.to_string(), value);
360            });
361            map
362        }
363
364        fn run(&self) {
365            self.base.run();
366            let input = Input::<S>::from(self.base.h);
367            let output = Output::from(self.base.gamma);
368            let sk = Secret::from_scalar(self.base.sk);
369            let (proof, blind) = sk.prove(input, output, &self.base.ad);
370            assert_eq!(self.blind, blind, "Blinding factor mismatch");
371            assert_eq!(self.proof.pk_com, proof.pk_com, "Proof pkb mismatch");
372            assert_eq!(self.proof.r, proof.r, "Proof r mismatch");
373            assert_eq!(self.proof.ok, proof.ok, "Proof ok mismatch");
374            assert_eq!(self.proof.s, proof.s, "Proof s mismatch");
375            assert_eq!(self.proof.sb, proof.sb, "Proof sb mismatch");
376
377            assert!(Public::verify(input, output, &self.base.ad, &proof).is_ok());
378        }
379    }
380}