Skip to main content

ark_vrf/
lib.rs

1//! # Elliptic Curve VRF
2//!
3//! Implementations of Verifiable Random Function with Additional Data (VRF-AD)
4//! schemes built on a transcript-based Fiat-Shamir transform with support for
5//! multiple input/output pairs via delinearization.
6//!
7//! Built on the [Arkworks](https://github.com/arkworks-rs) framework with
8//! configurable cryptographic parameters and `no_std` support.
9//!
10//! ## Security
11//!
12//! VRF input points **must** be constructed via hash-to-curve (e.g.
13//! [`Input::new`]) so that nobody knows their discrete-log relation to the
14//! generator `G`. If the prover knew such a relation, they could forge
15//! outputs. This is critical because the delinearization merges the Schnorr
16//! and VRF pairs into a single check.
17//!
18//! ## Schemes
19//!
20//! - **Tiny VRF**: Compact proof. Loosely inspired by
21//!   [RFC-9381](https://datatracker.ietf.org/doc/rfc9381), adapted with a
22//!   transcript-based Fiat-Shamir transform, support for additional data, and
23//!   multiple I/O pairs via delinearization.
24//!
25//! - **Thin VRF**: Same structure as Tiny VRF but stores the nonce commitment
26//!   instead of the challenge, enabling batch verification at the cost of a
27//!   slightly larger proof.
28//!
29//! - **Pedersen VRF**: Key-hiding VRF based on the construction introduced by
30//!   [BCHSV23](https://eprint.iacr.org/2023/002). Replaces the public key with a
31//!   Pedersen commitment to the secret key, serving as a building block for
32//!   anonymized ring signatures.
33//!
34//! - **Ring VRF**: Anonymized ring VRF combining Pedersen VRF with the ring proof
35//!   scheme derived from [CSSV22](https://eprint.iacr.org/2022/1362). Proves that
36//!   a single blinded key is a member of a committed ring without revealing which one.
37//!
38//! ### Specifications
39//!
40//! - [VRF Schemes](https://github.com/davxy/bandersnatch-vrf-spec)
41//! - [Ring Proof](https://github.com/davxy/ring-proof-spec)
42//!
43//! ## Built-In suites
44//!
45//! The library conditionally includes the following pre-configured suites (see features section):
46//!
47//! - **Ed25519**: Supports Tiny, Thin, and Pedersen VRF.
48//! - **Secp256r1**: Supports Tiny, Thin, and Pedersen VRF.
49//! - **Bandersnatch** (_Edwards curve on BLS12-381_): Supports Tiny, Thin, Pedersen, and Ring VRF.
50//! - **JubJub** (_Edwards curve on BLS12-381_): Supports Tiny, Thin, Pedersen, and Ring VRF.
51//! - **Baby-JubJub** (_Edwards curve on BN254_): Supports Tiny, Thin, Pedersen, and Ring VRF.
52//!
53//! ## Usage
54//!
55//! ```rust,ignore
56//! use ark_vrf::suites::bandersnatch::*;
57//!
58//! let secret = Secret::from_seed([0; 32]);
59//! let public = secret.public();
60//! let input = Input::new(b"example input").unwrap();
61//! let output = secret.output(input);
62//! let hash_bytes: [u8; 32] = output.hash();
63//! ```
64//!
65//! ## Features
66//!
67//! - `default`: `std`
68//! - `full`: Enables all features listed below except `secret-split`, `parallel`, `asm`, `test-vectors`.
69//! - `secret-split`: Split-secret scalar multiplication. Secret scalar is split into the sum
70//!   of two scalars, which randomly mutate but retain the same sum. Incurs 2x penalty in some internal
71//!   sensible scalar multiplications, but provides side channel defenses.
72//! - `ring`: Ring-VRF for the curves supporting it.
73//! - `test-vectors`: Deterministic ring-vrf proof. Useful for reproducible test vectors generation.
74//!
75//! ### Curves
76//!
77//! - `ed25519`
78//! - `jubjub`
79//! - `bandersnatch`
80//! - `baby-jubjub`
81//! - `secp256r1`
82//!
83//! ### Arkworks optimizations
84//!
85//! - `parallel`: Parallel execution where worth using `rayon`.
86//! - `asm`: Assembly implementation of some low level operations.
87//!
88//! ## License
89//!
90//! Distributed under the [MIT License](./LICENSE).
91
92#![cfg_attr(not(feature = "std"), no_std)]
93#![deny(unsafe_code)]
94
95use ark_ec::{AffineRepr, CurveGroup};
96use ark_ff::{PrimeField, Zero};
97use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
98use ark_std::vec::Vec;
99
100use utils::transcript::Transcript;
101use zeroize::Zeroize;
102
103pub mod pedersen;
104pub mod suites;
105pub mod thin;
106pub mod tiny;
107pub mod utils;
108
109#[cfg(feature = "ring")]
110pub mod ring;
111
112#[cfg(test)]
113mod testing;
114
115/// Re-export stuff that may be useful downstream.
116pub mod reexports {
117    pub use ark_ec;
118    pub use ark_ff;
119    pub use ark_serialize;
120    pub use ark_std;
121}
122
123/// Suite's affine curve point type.
124pub type AffinePoint<S> = <S as Suite>::Affine;
125/// Suite's base field type.
126pub type BaseField<S> = <AffinePoint<S> as AffineRepr>::BaseField;
127/// Suite's scalar field type.
128pub type ScalarField<S> = <AffinePoint<S> as AffineRepr>::ScalarField;
129/// Suite's curve configuration type.
130pub type CurveConfig<S> = <AffinePoint<S> as AffineRepr>::Config;
131
132/// Crate error type.
133#[derive(Debug)]
134pub enum Error {
135    /// Proof verification failed.
136    VerificationFailure,
137    /// Invalid input data (e.g. point not in the prime-order subgroup,
138    /// deserialization failure, ring size exceeding parameters).
139    InvalidData,
140}
141
142impl From<ark_serialize::SerializationError> for Error {
143    fn from(_err: ark_serialize::SerializationError) -> Self {
144        Error::InvalidData
145    }
146}
147
148/// Defines a cipher suite.
149///
150/// Configures the elliptic curve, transcript, and core operations (nonce
151/// generation, challenge derivation, hash-to-curve) for a VRF-AD scheme.
152/// The default implementations are inspired by RFC-9381 and RFC-8032 but
153/// use a pluggable [`Transcript`]-based Fiat-Shamir transform rather than
154/// the specific hash constructions prescribed by the RFC. Default methods
155/// can be overridden to implement custom VRF variants.
156pub trait Suite: Copy {
157    /// Suite identifier.
158    ///
159    /// A unique byte string used for transcript domain separation and as the
160    /// hash-to-curve DST prefix. The actual constructions a `SUITE_ID` stands
161    /// for are defined by the suite specification (see each suite's module
162    /// docs). Implementations targeting interop must use the same string.
163    const SUITE_ID: &'static [u8];
164
165    /// Curve point in affine representation.
166    ///
167    /// The point is guaranteed to be in the correct prime order subgroup
168    /// by the `AffineRepr` bound.
169    type Affine: AffineRepr;
170
171    /// Fiat-Shamir transcript.
172    ///
173    /// Provides absorb/squeeze interface for challenge generation,
174    /// nonce derivation, delinearization, and other hash-based operations.
175    type Transcript: Transcript;
176
177    /// Generator used through all the suite.
178    ///
179    /// Defaults to Arkworks provided generator.
180    #[inline(always)]
181    fn generator() -> AffinePoint<Self> {
182        Self::Affine::generator()
183    }
184
185    /// Generate a nonce scalar from the secret key and transcript state.
186    ///
187    /// The transcript typically carries shared state from `vrf_transcript`,
188    /// binding the nonce to the I/O pairs and additional data.
189    ///
190    /// Defaults to [`utils::nonce`] (deterministic, inspired by RFC-8032 section 5.1.6).
191    #[inline(always)]
192    fn nonce(sk: &ScalarField<Self>, transcript: Option<Self::Transcript>) -> ScalarField<Self> {
193        utils::nonce::<Self>(sk, transcript)
194    }
195
196    /// Derive a challenge scalar from curve points and transcript state.
197    ///
198    /// Absorbs curve points into the transcript and squeezes a scalar.
199    /// The transcript typically carries shared state from `vrf_transcript`.
200    ///
201    /// Defaults to [`utils::challenge`] (inspired by RFC-9381 section 5.4.3).
202    #[inline(always)]
203    fn challenge(
204        pts: &[&AffinePoint<Self>],
205        transcript: Option<Self::Transcript>,
206    ) -> ScalarField<Self> {
207        utils::challenge::<Self>(pts, transcript)
208    }
209
210    /// Hash data to a curve point.
211    ///
212    /// The input `data` is the raw pre-image; any salting must be applied
213    /// by the caller before invoking this method.
214    ///
215    /// Defaults to [`utils::hash_to_curve_tai`] (try-and-increment).
216    /// Override for alternative methods like [`utils::hash_to_curve_ell2_xmd`] (Elligator2).
217    #[inline(always)]
218    fn data_to_point(data: &[u8]) -> Option<AffinePoint<Self>> {
219        utils::hash_to_curve_tai::<Self>(data)
220    }
221
222    /// Map a curve point to a hash value.
223    ///
224    /// Defaults to [`utils::point_to_hash`].
225    #[inline(always)]
226    fn point_to_hash<const N: usize>(pt: &AffinePoint<Self>) -> [u8; N] {
227        utils::point_to_hash::<Self, N>(pt, false)
228    }
229}
230
231/// Secret key for VRF operations.
232///
233/// Contains the private scalar and cached public key.
234/// Implements automatic zeroization on drop.
235#[derive(Debug, Clone, PartialEq)]
236pub struct Secret<S: Suite> {
237    /// Secret scalar.
238    pub(crate) scalar: ScalarField<S>,
239    /// Cached public key.
240    pub(crate) public: Public<S>,
241}
242
243impl<S: Suite> Drop for Secret<S> {
244    fn drop(&mut self) {
245        self.scalar.zeroize()
246    }
247}
248
249impl<S: Suite> CanonicalSerialize for Secret<S> {
250    fn serialize_with_mode<W: ark_std::io::prelude::Write>(
251        &self,
252        writer: W,
253        compress: ark_serialize::Compress,
254    ) -> Result<(), ark_serialize::SerializationError> {
255        self.scalar.serialize_with_mode(writer, compress)
256    }
257
258    fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
259        self.scalar.serialized_size(compress)
260    }
261}
262
263impl<S: Suite> CanonicalDeserialize for Secret<S> {
264    fn deserialize_with_mode<R: ark_std::io::prelude::Read>(
265        reader: R,
266        compress: ark_serialize::Compress,
267        validate: ark_serialize::Validate,
268    ) -> Result<Self, ark_serialize::SerializationError> {
269        let scalar = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
270            reader, compress, validate,
271        )?;
272        Ok(Self::from_scalar(scalar))
273    }
274}
275
276impl<S: Suite> ark_serialize::Valid for Secret<S> {
277    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
278        self.scalar.check()
279    }
280}
281
282impl<S: Suite> Secret<S> {
283    /// Construct a `Secret` from the given scalar.
284    pub fn from_scalar(scalar: ScalarField<S>) -> Self {
285        let public = Public((S::generator() * scalar).into_affine());
286        Self { scalar, public }
287    }
288
289    /// Derives a `Secret` scalar deterministically from a seed.
290    ///
291    /// The seed is hashed using the suite's transcript, and the output is
292    /// reduced modulo the curve's order to produce a valid scalar in the
293    /// range `[1, n - 1]`. No clamping or multiplication by the cofactor is
294    /// performed, regardless of the curve.
295    ///
296    /// The caller is responsible for ensuring that the resulting scalar is
297    /// used safely with respect to the target curve's cofactor and subgroup
298    /// properties.
299    pub fn from_seed(seed: [u8; 32]) -> Self {
300        let mut cnt = 0_u8;
301        let sk = ScalarField::<S>::from_le_bytes_mod_order(&seed);
302        let scalar = loop {
303            let mut transcript = S::Transcript::new(S::SUITE_ID);
304            transcript.absorb_raw(&seed);
305            if cnt > 0 {
306                transcript.absorb_raw(&[cnt]);
307            }
308            let scalar = utils::nonce::<S>(&sk, Some(transcript.clone()));
309            if !scalar.is_zero() {
310                break scalar;
311            }
312            // Reaching 256 consecutive zero scalars is unreachable under
313            // standard assumptions on the transcript hash (probability
314            // ≈ 2^(-65000)); hitting it implies a broken primitive.
315            cnt = cnt
316                .checked_add(1)
317                .expect("unreachable: transcript hash produced 256 consecutive zero scalars");
318        };
319        Self::from_scalar(scalar)
320    }
321
322    /// Construct an ephemeral `Secret` using the provided randomness source.
323    pub fn from_rand(rng: &mut impl ark_std::rand::RngCore) -> Self {
324        let mut seed = [0u8; 32];
325        rng.fill_bytes(&mut seed);
326        Self::from_seed(seed)
327    }
328
329    /// Get the secret scalar.
330    pub fn scalar(&self) -> &ScalarField<S> {
331        &self.scalar
332    }
333
334    /// Get the associated public key.
335    pub fn public(&self) -> Public<S> {
336        self.public
337    }
338
339    /// Get the VRF output point relative to input.
340    pub fn output(&self, input: Input<S>) -> Output<S> {
341        Output(smul!(input.0, self.scalar).into_affine())
342    }
343
344    /// Get the VRF input-output pair relative to input.
345    pub fn vrf_io(&self, input: Input<S>) -> VrfIo<S> {
346        VrfIo {
347            input,
348            output: self.output(input),
349        }
350    }
351}
352
353/// Public key generic over the cipher suite.
354///
355/// Elliptic curve point representing the public component of a VRF key pair.
356#[derive(Debug, Copy, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
357pub struct Public<S: Suite>(pub AffinePoint<S>);
358
359impl<S: Suite> Public<S> {
360    /// Construct from an affine point with subgroup validation.
361    ///
362    /// Returns `Error::InvalidData` if the point is not in the prime-order subgroup.
363    pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
364        ark_serialize::Valid::check(&value).map_err(|_| Error::InvalidData)?;
365        Ok(Self(value))
366    }
367
368    /// Construct from an affine point without subgroup checks.
369    ///
370    /// The caller must ensure `value` is in the prime-order subgroup.
371    pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
372        Self(value)
373    }
374}
375
376/// VRF input point generic over the cipher suite.
377///
378/// Elliptic curve point representing the VRF input.
379#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
380pub struct Input<S: Suite>(pub AffinePoint<S>);
381
382impl<S: Suite> Input<S> {
383    /// Construct from [`Suite::data_to_point`].
384    ///
385    /// Maps arbitrary data to a curve point via hash-to-curve.
386    pub fn new(data: &[u8]) -> Option<Self> {
387        S::data_to_point(data).map(Input)
388    }
389}
390
391impl<S: Suite> Input<S> {
392    /// Construct from an affine point with subgroup validation.
393    ///
394    /// Returns `Error::InvalidData` if the point is not in the prime-order subgroup.
395    ///
396    /// Note: this only validates subgroup membership, not that the point was
397    /// produced by hash-to-curve. The caller is still responsible for ensuring
398    /// the point is not in a known discrete-log relation with the suite
399    /// generator (required for Thin-VRF soundness).
400    pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
401        ark_serialize::Valid::check(&value).map_err(|_| Error::InvalidData)?;
402        Ok(Self(value))
403    }
404
405    /// Construct from an affine point without subgroup checks.
406    ///
407    /// # Safety
408    ///
409    /// The caller must ensure that `value` is in the prime-order subgroup and
410    /// was produced by a hash-to-curve procedure (or is otherwise not in a
411    /// known discrete-log relation with the suite generator). The latter is
412    /// required for the soundness of schemes like Thin-VRF where the input
413    /// and generator are delinearized into a single check.
414    pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
415        Self(value)
416    }
417}
418
419/// VRF output point generic over the cipher suite.
420///
421/// Elliptic curve point representing the VRF output.
422#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
423pub struct Output<S: Suite>(pub AffinePoint<S>);
424
425impl<S: Suite> Output<S> {
426    /// Construct from an affine point with subgroup validation.
427    ///
428    /// Returns `Error::InvalidData` if the point is not in the prime-order subgroup.
429    pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
430        ark_serialize::Valid::check(&value).map_err(|_| Error::InvalidData)?;
431        Ok(Self(value))
432    }
433
434    /// Construct from an affine point without subgroup checks.
435    ///
436    /// The caller must ensure `value` is in the prime-order subgroup.
437    pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
438        Self(value)
439    }
440}
441
442impl<S: Suite> Output<S> {
443    /// Hash the output point to a deterministic byte string.
444    pub fn hash<const N: usize>(&self) -> [u8; N] {
445        S::point_to_hash(&self.0)
446    }
447}
448
449/// VRF input-output pair.
450#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
451pub struct VrfIo<S: Suite> {
452    pub input: Input<S>,
453    pub output: Output<S>,
454}
455
456impl<S: Suite> AsRef<[VrfIo<S>]> for VrfIo<S> {
457    fn as_ref(&self) -> &[VrfIo<S>] {
458        core::slice::from_ref(self)
459    }
460}
461
462/// Type aliases for the given suite.
463#[macro_export]
464macro_rules! suite_types {
465    ($suite:ident) => {
466        #[allow(dead_code)]
467        pub type Secret = $crate::Secret<$suite>;
468        #[allow(dead_code)]
469        pub type Public = $crate::Public<$suite>;
470        #[allow(dead_code)]
471        pub type Input = $crate::Input<$suite>;
472        #[allow(dead_code)]
473        pub type Output = $crate::Output<$suite>;
474        #[allow(dead_code)]
475        pub type AffinePoint = $crate::AffinePoint<$suite>;
476        #[allow(dead_code)]
477        pub type ScalarField = $crate::ScalarField<$suite>;
478        #[allow(dead_code)]
479        pub type BaseField = $crate::BaseField<$suite>;
480        #[allow(dead_code)]
481        pub type TinyProof = $crate::tiny::Proof<$suite>;
482        #[allow(dead_code)]
483        pub type PedersenProof = $crate::pedersen::Proof<$suite>;
484        #[allow(dead_code)]
485        pub type PedersenBatchItem = $crate::pedersen::BatchItem<$suite>;
486        #[allow(dead_code)]
487        pub type PedersenBatchVerifier = $crate::pedersen::BatchVerifier<$suite>;
488        #[allow(dead_code)]
489        pub type ThinProof = $crate::thin::Proof<$suite>;
490        #[allow(dead_code)]
491        pub type ThinBatchItem = $crate::thin::BatchItem<$suite>;
492        #[allow(dead_code)]
493        pub type ThinBatchVerifier = $crate::thin::BatchVerifier<$suite>;
494        #[allow(dead_code)]
495        pub type VrfIo = $crate::VrfIo<$suite>;
496    };
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502    use crate::tiny::{Prover, Verifier};
503    use ark_ec::AffineRepr;
504    use suites::testing::{Input, Secret, TestSuite};
505    use testing::{TEST_SEED, random_val};
506
507    #[test]
508    fn vrf_output_check() {
509        use ark_std::rand::SeedableRng;
510        let mut rng = ark_std::rand::rngs::StdRng::from_seed([42; 32]);
511        let secret = Secret::from_seed(TEST_SEED);
512        let input = Input::from_affine_unchecked(random_val(Some(&mut rng)));
513        let output = secret.output(input);
514
515        let expected = "4af9bf572a107a8f61faa380667efe27eaf399cc8e718d57ef328924eb51d450";
516        assert_eq!(expected, hex::encode(output.hash::<32>()));
517    }
518
519    #[test]
520    fn prove_uniqueness_vulnerability() {
521        use ark_ff::BigInteger;
522        use ark_std::{One, Zero};
523        use utils::common::{DomSep, ExactChain};
524
525        type S = TestSuite;
526        type Sc = ScalarField<S>;
527
528        let secret = crate::Secret::<S>::from_seed(TEST_SEED);
529        let public = secret.public();
530        let input = Input::new(b"uniqueness attack").unwrap();
531        let honest_output = secret.output(input);
532
533        // 1. Find a low-order point L (order 2 for Ed25519)
534        // For Ed25519, (0, -1) is order 2.
535        let low_order_pt =
536            AffinePoint::<S>::new_unchecked(BaseField::<S>::zero(), -BaseField::<S>::one());
537        assert!(!low_order_pt.is_zero());
538        // Verify it's order 2: 2 * L = O
539        assert!((low_order_pt.into_group() + low_order_pt.into_group()).is_zero());
540
541        // 2. Compute gamma' = gamma + L
542        let malicious_output =
543            Output::from_affine_unchecked((honest_output.0 + low_order_pt).into_affine());
544        assert_ne!(honest_output, malicious_output);
545        assert_ne!(honest_output.hash::<32>(), malicious_output.hash::<32>());
546
547        // 3. Forge a proof by grinding k until c*z_1 is even (so c*z_1*L = 0)
548        //
549        // The verify equation for the VRF I/O part is s*I_m - c*O_m = k*I_m,
550        // where O_m includes z_1*(O_honest + L). For this to hold we need
551        // c*z_1*L = 0, i.e. c*z_1 must be even (since L has order 2).
552        // Since c is odd (ground below) we also need z_1 to be even.
553        // z_1 is the delinearization scalar determined by (pk, ios, ad), so
554        // we iterate over ad values to find one where z_1 is even.
555        let malicious_io = VrfIo {
556            input,
557            output: malicious_output,
558        };
559        let mal_ios = [malicious_io];
560
561        // Search for an ad that produces an even delinearization scalar z_1.
562        let mut ad_ctr = 0u32;
563        let (ad, t, merged_input) = loop {
564            let ad = format!("ad-{ad_ctr}");
565            let schnorr = core::iter::once(VrfIo {
566                input: Input(S::generator()),
567                output: Output(public.0),
568            });
569            let chain = ExactChain::new(schnorr, mal_ios.iter().copied());
570            let (t, zs) =
571                utils::vrf_transcript_scalars_from_iter(DomSep::TinyVrf, chain, ad.as_bytes());
572            // z_1 is the delinearization scalar for the VRF pair
573            if zs[1].into_bigint().is_even() {
574                // Compute merged input: I_m = z_0*G + z_1*I
575                let i_m = (S::generator() * zs[0] + input.0 * zs[1]).into_affine();
576                break (ad, t, i_m);
577            }
578            ad_ctr += 1;
579            assert!(ad_ctr < 100, "Failed to find suitable ad");
580        };
581
582        // Now grind k to get an odd challenge c (so that q-c is even, i.e. (-c)*L = 0).
583        let mut ctr = 0u64;
584        let proof = loop {
585            let mut k_seed = [0u8; 8];
586            k_seed.copy_from_slice(&ctr.to_le_bytes());
587            let k = Sc::from_le_bytes_mod_order(&k_seed);
588
589            // R = k * I_m (merged input including Schnorr pair)
590            let r = (merged_input * k).into_affine();
591
592            let c = S::challenge(&[&r], Some(t.clone()));
593
594            if !c.into_bigint().is_even() {
595                let s = k + c * secret.scalar;
596                break crate::tiny::Proof { c, s };
597            }
598            ctr += 1;
599            assert!(ctr <= 1000, "Grinding failed");
600        };
601
602        // 4. Verify the malicious proof
603        assert!(public.verify(malicious_io, ad.as_bytes(), &proof).is_ok());
604
605        // 5. Verify the honest proof still works
606        let honest_io = VrfIo {
607            input,
608            output: honest_output,
609        };
610        let honest_proof = secret.prove(honest_io, ad.as_bytes());
611        assert!(
612            public
613                .verify(honest_io, ad.as_bytes(), &honest_proof)
614                .is_ok()
615        );
616
617        // Two different outputs for the same input and public key.
618        assert_ne!(honest_output.hash::<32>(), malicious_output.hash::<32>());
619    }
620}