Skip to main content

dkls23_core/utilities/
proofs.rs

1//! Zero-knowledge proofs required by the protocols.
2//!
3//! This file implements some protocols for zero-knowledge proofs over the
4//! curve secp256k1.
5//!
6//! The main protocol is for proofs of discrete logarithms. It is used during
7//! key generation in the `DKLs23` protocol (<https://eprint.iacr.org/2023/765.pdf>).
8//!
9//! For the base OT in the OT extension, we use the endemic protocol of Zhou et al.
10//! (see Section 3 of <https://eprint.iacr.org/2022/1525.pdf>). Thus, we also include
11//! another zero knowledge proof employing the Chaum-Pedersen protocol, the
12//! OR-composition and the Fiat-Shamir transform (as in their paper).
13//!
14//! # Discrete Logarithm Proof
15//!
16//! We implement Schnorr's protocol together with a randomized Fischlin transform
17//! (see [`DLogProof`]).
18//!
19//! We base our implementation on Figures 23 and 27 of Zhou et al.
20//!
21//! For convenience, instead of writing the protocol directly, we wrote first an
22//! implementation of the usual Schnorr's protocol, which is interactive (see [`InteractiveDLogProof`]).
23//! Since it will be used for the non-interactive version, we made same particular choices
24//! that would not make much sense if this interactive proof were used alone.
25//!
26//! # Encryption Proof
27//!
28//! The OT protocol of Zhou et al. uses an `ElGamal` encryption at some point
29//! and it needs a zero-knowledge proof to verify its correctness.
30//!
31//! This implementation follows their paper: see page 17 and Appendix B.
32//!
33//! IMPORTANT: As specified in page 30 of `DKLs23`, we instantiate the protocols
34//! above over the same elliptic curve group used in our main protocol.
35
36use elliptic_curve::ops::Reduce;
37use elliptic_curve::FieldBytes;
38use rand::{Rng, RngExt};
39use rustcrypto_ff::Field;
40use rustcrypto_group::prime::PrimeCurveAffine;
41use rustcrypto_group::Curve as GroupCurve;
42use std::collections::HashSet;
43use std::marker::PhantomData;
44
45use crate::curve::DklsCurve;
46use crate::utilities::hashes::{
47    point_to_bytes, scalar_to_bytes, tagged_hash, tagged_hash_as_scalar, HashOutput,
48};
49use std::fmt;
50
51use crate::utilities::oracle_tags::{
52    TAG_DLOG_PROOF_COMMITMENT, TAG_DLOG_PROOF_FISCHLIN, TAG_ENCPROOF_FS,
53};
54use crate::utilities::rng;
55use subtle::ConstantTimeEq;
56
57/// Error returned when the Fischlin proof-of-work search is exhausted
58/// without finding all required hash collisions.
59#[derive(Debug, Clone)]
60pub struct ProofSearchExhausted;
61
62impl fmt::Display for ProofSearchExhausted {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(
65            f,
66            "Fischlin proof-of-work search exhausted without finding all required hash collisions"
67        )
68    }
69}
70
71impl std::error::Error for ProofSearchExhausted {}
72
73/// Constants for the randomized Fischlin transform.
74pub const R: u16 = 64;
75pub const L: u16 = 4;
76pub const T: u16 = 32;
77
78// DISCRETE LOGARITHM PROOF.
79
80/// Schnorr's protocol (interactive).
81#[derive(Debug, Clone)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[cfg_attr(
84    feature = "serde",
85    serde(bound(
86        serialize = "C::Scalar: serde::Serialize",
87        deserialize = "C::Scalar: serde::Deserialize<'de>"
88    ))
89)]
90pub struct InteractiveDLogProof<C: DklsCurve> {
91    pub challenge: Vec<u8>,
92    pub challenge_response: C::Scalar,
93    #[cfg_attr(feature = "serde", serde(skip))]
94    _curve: PhantomData<C>,
95}
96
97/// Convert a short challenge byte slice (at most T/8 bytes) to a scalar
98/// by zero-extending to 32 bytes and reducing via `Reduce<FieldBytes<C>>`.
99fn challenge_to_scalar<C: DklsCurve>(challenge: &[u8]) -> C::Scalar {
100    let mut extended = vec![0u8; 32 - challenge.len()];
101    extended.extend_from_slice(challenge);
102    // FieldBytes<C> is 32 bytes for both secp256k1 and P-256.
103    let field_bytes: &FieldBytes<C> = extended
104        .as_slice()
105        .try_into()
106        .expect("extended challenge length matches field size");
107    <C::Scalar as Reduce<FieldBytes<C>>>::reduce(field_bytes)
108}
109
110impl<C: DklsCurve> InteractiveDLogProof<C> {
111    /// Step 1 - Samples the random commitments.
112    ///
113    /// The `Scalar` is kept secret while the `AffinePoint` is transmitted.
114    #[must_use]
115    pub fn prove_step1(mut rng: impl Rng) -> (C::Scalar, C::AffinePoint) {
116        // We sample a nonzero random scalar.
117        let mut scalar_rand_commitment = <C::Scalar as Field>::ZERO;
118        while scalar_rand_commitment == <C::Scalar as Field>::ZERO {
119            scalar_rand_commitment = <C::Scalar as Field>::random(&mut rng);
120        }
121
122        let generator = <C::AffinePoint as PrimeCurveAffine>::generator();
123        let point_rand_commitment = (generator * scalar_rand_commitment).to_affine();
124
125        (scalar_rand_commitment, point_rand_commitment)
126    }
127
128    /// Step 2 - Computes the response for a given challenge.
129    ///
130    /// Here, `scalar` is the witness for the proof and `scalar_rand_commitment`
131    /// is the secret value from the previous step.
132    #[must_use]
133    pub fn prove_step2(
134        scalar: &C::Scalar,
135        scalar_rand_commitment: &C::Scalar,
136        challenge: &[u8],
137    ) -> InteractiveDLogProof<C> {
138        // For convenience, we are using a challenge in bytes.
139        // We convert it back to a scalar.
140        // The challenge will have T bits, so we first extend it to 256 bits.
141        let challenge_scalar = challenge_to_scalar::<C>(challenge);
142
143        // We compute the response.
144        let challenge_response = *scalar_rand_commitment - (challenge_scalar * scalar);
145
146        InteractiveDLogProof {
147            challenge: challenge.to_vec(), // We save the challenge for the next protocol.
148            challenge_response,
149            _curve: PhantomData,
150        }
151    }
152
153    /// Verification of a proof.
154    ///
155    /// The variable `point` is the point used for the proof.
156    /// We didn't include it in the struct in order to not make unnecessary
157    /// repetitions in the main protocol.
158    ///
159    /// Attention: the challenge should enter as a parameter here, but in the
160    /// next protocol, it will come from the prover, so we decided to save it
161    /// inside the struct.
162    #[must_use]
163    pub fn verify(&self, point: &C::AffinePoint, point_rand_commitment: &C::AffinePoint) -> bool {
164        // Challenges are expected to be short (in this implementation they are 1 byte),
165        // and must never exceed T/8 bytes.
166        if self.challenge.is_empty() || self.challenge.len() > (T / 8) as usize {
167            return false;
168        }
169
170        // For convenience, we are using a challenge in bytes.
171        // We convert it back to a scalar.
172        // The challenge will have T bits, so we first extend it to 256 bits.
173        let challenge_scalar = challenge_to_scalar::<C>(&self.challenge);
174
175        let generator = <C::AffinePoint as PrimeCurveAffine>::generator();
176
177        // We compare the values that should agree.
178        let point_verify =
179            ((generator * self.challenge_response) + (*point * challenge_scalar)).to_affine();
180
181        point_verify == *point_rand_commitment
182    }
183}
184
185/// Schnorr's protocol (non-interactive via randomized Fischlin transform).
186///
187/// In order to remove interaction, we employ the "randomized Fischlin transform"
188/// described in Figure 9 of <https://eprint.iacr.org/2022/393.pdf>. However, we will
189/// follow the approach in Figure 27 of <https://eprint.iacr.org/2022/1525.pdf>.
190/// It seems to come from Section 5.1 of the first paper.
191///
192/// There are some errors in this description (for example, `xi_i` and `xi_{i+r/2}`
193/// are always the empty set), and thus we adapt Figure 9 of the first article. There is
194/// still a problem: the paper says to choose `r` and `l` such that, in particular, `rl = 2^lambda`.
195/// If `lambda = 256`, then `r` or `l` are astronomically large and the protocol becomes
196/// computationally infeasible. We will use instead the condition `rl = lambda`.
197/// We believe this is what the authors wanted, since this condition appears
198/// in most of the rest of the first paper.
199///
200/// With `lambda = 256`, we chose `r = 64` and `l = 4` (higher values of `l` were too slow).
201/// In this case, the constant `t` from the paper is equal to 32.
202#[derive(Debug, Clone)]
203#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
204#[cfg_attr(
205    feature = "serde",
206    serde(bound(
207        serialize = "C::AffinePoint: serde::Serialize, C::Scalar: serde::Serialize",
208        deserialize = "C::AffinePoint: serde::Deserialize<'de>, C::Scalar: serde::Deserialize<'de>"
209    ))
210)]
211pub struct DLogProof<C: DklsCurve> {
212    pub point: C::AffinePoint,
213    pub rand_commitments: Vec<C::AffinePoint>,
214    pub proofs: Vec<InteractiveDLogProof<C>>,
215}
216
217impl<C: DklsCurve> DLogProof<C> {
218    /// Computes a proof for the witness `scalar`.
219    ///
220    /// Returns an error if the Fischlin proof-of-work search is exhausted
221    /// without finding all required hash collisions. This is astronomically
222    /// unlikely with a correct RNG but must not be silently ignored.
223    pub fn prove(
224        scalar: &C::Scalar,
225        session_id: &[u8],
226    ) -> Result<DLogProof<C>, ProofSearchExhausted> {
227        // We execute Step 1 r times.
228        let mut rand_commitments: Vec<C::AffinePoint> = Vec::with_capacity(R as usize);
229        let mut states: Vec<C::Scalar> = Vec::with_capacity(R as usize);
230        let mut rng = rng::get_rng();
231        for _ in 0..R {
232            let (state, rand_commitment) = InteractiveDLogProof::<C>::prove_step1(&mut rng);
233
234            rand_commitments.push(rand_commitment);
235            states.push(state);
236        }
237
238        // We save this vector in bytes.
239        let rc_as_bytes = rand_commitments
240            .clone()
241            .into_iter()
242            .map(|x| point_to_bytes::<C>(&x))
243            .collect::<Vec<Vec<u8>>>()
244            .concat();
245
246        // Now, there is a "proof of work".
247        // We have to find the good challenges.
248        let mut first_proofs: Vec<InteractiveDLogProof<C>> = Vec::with_capacity((R / 2) as usize);
249        let mut last_proofs: Vec<InteractiveDLogProof<C>> = Vec::with_capacity((R / 2) as usize);
250        for i in 0..(R / 2) {
251            // We will find different challenges until one of them works.
252            // Since both hashes to be computed are of 2l bits, we expect
253            // them to coincide after 2^{2l} tries (assuming everything is
254            // uniformly random and independent). For l = 4, this is just
255            // 256 tries. For safety, we will put a large margin and repeat
256            // each while at most 2^16 times (so 2^32 tries in total).
257
258            let mut flag = false;
259            let mut first_counter = 0u16;
260            while first_counter < u16::MAX && !flag {
261                // We sample an array of T bits = T/8 bytes.
262                let first_challenge = rng::get_rng().random::<[u8; (T / 8) as usize]>();
263
264                // If this challenge was already sampled, we should go back.
265                // However, with some tests, we saw that it is time consuming
266                // to save the challenges (we have to reallocate memory all the
267                // time when increasing the vector of used challenges).
268
269                // Fortunately, note that our sample space has cardinality 2^t
270                // (which is 2^32 in our case), and we repeat the loop 2^16 times.
271                // Even if in all iterations we sample different values, the
272                // probability of getting an older challenge in an additional
273                // iteration is 2^16/2^32, which is small. Thus, we don't expect
274                // a lot of repetitions.
275
276                // We execute Step 2 at index i.
277                let first_proof = InteractiveDLogProof::<C>::prove_step2(
278                    scalar,
279                    &states[i as usize],
280                    &first_challenge,
281                );
282
283                // Let's take the first hash here.
284                let generator = <C::AffinePoint as PrimeCurveAffine>::generator();
285                let first_msg = [
286                    &point_to_bytes::<C>(&generator),
287                    &rc_as_bytes[..],
288                    &i.to_be_bytes(),
289                    &first_challenge,
290                    &scalar_to_bytes::<C>(&first_proof.challenge_response),
291                ]
292                .concat();
293                // The random oracle has to return an array of 2l bits = l/4 bytes, so we take a slice.
294                let first_hash = &tagged_hash(TAG_DLOG_PROOF_FISCHLIN, &[session_id, &first_msg])
295                    [0..(L / 4) as usize];
296
297                // Now comes the search for the next challenge.
298                let mut second_counter = 0u16;
299                let mut rng = rng::get_rng();
300                while second_counter < u16::MAX {
301                    // We sample another array. Same considerations as before.
302                    let second_challenge = rng.random::<[u8; (T / 8) as usize]>();
303
304                    //if used_second_challenges.contains(&second_challenge) { continue; }
305
306                    // We execute Step 2 at index i + R/2.
307                    let second_proof = InteractiveDLogProof::<C>::prove_step2(
308                        scalar,
309                        &states[(i + (R / 2)) as usize],
310                        &second_challenge,
311                    );
312
313                    // Second hash now.
314                    let generator = <C::AffinePoint as PrimeCurveAffine>::generator();
315                    let second_msg = [
316                        &point_to_bytes::<C>(&generator),
317                        &rc_as_bytes[..],
318                        &(i + (R / 2)).to_be_bytes(),
319                        &second_challenge,
320                        &scalar_to_bytes::<C>(&second_proof.challenge_response),
321                    ]
322                    .concat();
323                    let second_hash =
324                        &tagged_hash(TAG_DLOG_PROOF_FISCHLIN, &[session_id, &second_msg])
325                            [0..(L / 4) as usize];
326
327                    // If the hashes are equal, we are successful and we can break both loops.
328                    if *first_hash == *second_hash {
329                        // We save the successful results.
330                        first_proofs.push(first_proof);
331                        last_proofs.push(second_proof);
332
333                        // We update the flag to break the outer loop.
334                        flag = true;
335
336                        break;
337                    }
338
339                    // If we were not successful, we try again.
340                    second_counter += 1;
341                }
342
343                // If we were not successful, we try again.
344                first_counter += 1;
345            }
346
347            if !flag {
348                return Err(ProofSearchExhausted);
349            }
350        }
351
352        // We put together the vectors.
353        let proofs = [first_proofs, last_proofs].concat();
354
355        // We save the point.
356        let generator = <C::AffinePoint as PrimeCurveAffine>::generator();
357        let point = (generator * scalar).to_affine();
358
359        Ok(DLogProof {
360            point,
361            rand_commitments,
362            proofs,
363        })
364    }
365
366    /// Verification of a proof of discrete logarithm.
367    ///
368    /// Note that the point to be verified is in `proof`.
369    #[must_use]
370    pub fn verify(proof: &DLogProof<C>, session_id: &[u8]) -> bool {
371        // We first verify that all vectors have the correct length.
372        // If the prover is very unlucky, there is the possibility that
373        // he doesn't return all the needed proofs.
374        if proof.rand_commitments.len() != (R as usize) || proof.proofs.len() != (R as usize) {
375            return false;
376        }
377
378        // We transform the random commitments into bytes.
379        let vec_rc_as_bytes = proof
380            .rand_commitments
381            .clone()
382            .into_iter()
383            .map(|x| point_to_bytes::<C>(&x))
384            .collect::<Vec<Vec<u8>>>();
385
386        // All the proofs should be different (otherwise, it would be easier to forge a proof).
387        // Here we compare the random commitments using a HashSet.
388        let mut without_repetitions: HashSet<Vec<u8>> = HashSet::with_capacity(R as usize);
389        if !vec_rc_as_bytes
390            .clone()
391            .into_iter()
392            .all(move |x| without_repetitions.insert(x))
393        {
394            return false;
395        }
396
397        // We concatenate the vector of random commitments.
398        let rc_as_bytes = vec_rc_as_bytes.concat();
399
400        let generator = <C::AffinePoint as PrimeCurveAffine>::generator();
401        for i in 0..(R / 2) {
402            // We compare the hashes
403            let first_msg = [
404                &point_to_bytes::<C>(&generator),
405                &rc_as_bytes[..],
406                &i.to_be_bytes(),
407                &proof.proofs[i as usize].challenge,
408                &scalar_to_bytes::<C>(&proof.proofs[i as usize].challenge_response),
409            ]
410            .concat();
411            let first_hash = &tagged_hash(TAG_DLOG_PROOF_FISCHLIN, &[session_id, &first_msg])
412                [0..(L / 4) as usize];
413
414            let second_msg = [
415                &point_to_bytes::<C>(&generator),
416                &rc_as_bytes[..],
417                &(i + (R / 2)).to_be_bytes(),
418                &proof.proofs[(i + (R / 2)) as usize].challenge,
419                &scalar_to_bytes::<C>(&proof.proofs[(i + (R / 2)) as usize].challenge_response),
420            ]
421            .concat();
422            let second_hash = &tagged_hash(TAG_DLOG_PROOF_FISCHLIN, &[session_id, &second_msg])
423                [0..(L / 4) as usize];
424
425            // Constant-time comparison to prevent timing side-channels
426            // from leaking information about the proof structure.
427            if !bool::from(first_hash.ct_eq(second_hash)) {
428                return false;
429            }
430
431            // We verify both proofs.
432            let verification_1 =
433                proof.proofs[i as usize].verify(&proof.point, &proof.rand_commitments[i as usize]);
434            let verification_2 = proof.proofs[(i + (R / 2)) as usize].verify(
435                &proof.point,
436                &proof.rand_commitments[(i + (R / 2)) as usize],
437            );
438
439            if !verification_1 || !verification_2 {
440                return false;
441            }
442        }
443
444        // If we got here, all the previous tests passed.
445        true
446    }
447
448    /// Produces an instance of `DLogProof` (for the witness `scalar`)
449    /// together with a commitment (its hash).
450    ///
451    /// The commitment is transmitted first and the proof is sent later
452    /// when needed.
453    /// Computes a proof with a commitment, propagating proof generation errors.
454    pub fn prove_commit(
455        scalar: &C::Scalar,
456        session_id: &[u8],
457    ) -> Result<(DLogProof<C>, HashOutput), ProofSearchExhausted> {
458        let proof = Self::prove(scalar, session_id)?;
459
460        //Computes the commitment (it's the hash of DLogProof in bytes).
461        let point_as_bytes = point_to_bytes::<C>(&proof.point);
462        let rc_as_bytes = proof
463            .rand_commitments
464            .clone()
465            .into_iter()
466            .map(|x| point_to_bytes::<C>(&x))
467            .collect::<Vec<Vec<u8>>>()
468            .concat();
469        let challenges_as_bytes = proof
470            .proofs
471            .clone()
472            .into_iter()
473            .map(|x| x.challenge)
474            .collect::<Vec<Vec<u8>>>()
475            .concat();
476        let responses_as_bytes = proof
477            .proofs
478            .clone()
479            .into_iter()
480            .map(|x| scalar_to_bytes::<C>(&x.challenge_response))
481            .collect::<Vec<Vec<u8>>>()
482            .concat();
483
484        let msg_for_commitment = [
485            point_as_bytes,
486            rc_as_bytes,
487            challenges_as_bytes,
488            responses_as_bytes,
489        ]
490        .concat();
491        let commitment = tagged_hash(
492            TAG_DLOG_PROOF_COMMITMENT,
493            &[session_id, &msg_for_commitment],
494        );
495
496        Ok((proof, commitment))
497    }
498
499    /// Verifies a proof and checks it against the commitment.
500    #[must_use]
501    pub fn decommit_verify(
502        proof: &DLogProof<C>,
503        commitment: &HashOutput,
504        session_id: &[u8],
505    ) -> bool {
506        //Computes the expected commitment
507        let point_as_bytes = point_to_bytes::<C>(&proof.point);
508        let rc_as_bytes = proof
509            .rand_commitments
510            .clone()
511            .into_iter()
512            .map(|x| point_to_bytes::<C>(&x))
513            .collect::<Vec<Vec<u8>>>()
514            .concat();
515        let challenges_as_bytes = proof
516            .proofs
517            .clone()
518            .into_iter()
519            .map(|x| x.challenge)
520            .collect::<Vec<Vec<u8>>>()
521            .concat();
522        let responses_as_bytes = proof
523            .proofs
524            .clone()
525            .into_iter()
526            .map(|x| scalar_to_bytes::<C>(&x.challenge_response))
527            .collect::<Vec<Vec<u8>>>()
528            .concat();
529
530        let msg_for_commitment = [
531            point_as_bytes,
532            rc_as_bytes,
533            challenges_as_bytes,
534            responses_as_bytes,
535        ]
536        .concat();
537        let expected_commitment = tagged_hash(
538            TAG_DLOG_PROOF_COMMITMENT,
539            &[session_id, &msg_for_commitment],
540        );
541
542        bool::from(commitment.ct_eq(&expected_commitment)) && Self::verify(proof, session_id)
543    }
544}
545
546// ENCRYPTION PROOF
547
548/// Represents the random commitments for the Chaum-Pedersen protocol.
549#[derive(Debug, Clone)]
550#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
551#[cfg_attr(
552    feature = "serde",
553    serde(bound(
554        serialize = "C::AffinePoint: serde::Serialize",
555        deserialize = "C::AffinePoint: serde::Deserialize<'de>"
556    ))
557)]
558pub struct RandomCommitments<C: DklsCurve> {
559    pub rc_g: C::AffinePoint,
560    pub rc_h: C::AffinePoint,
561}
562
563/// Chaum-Pedersen protocol (interactive version).
564#[derive(Debug, Clone)]
565#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
566#[cfg_attr(
567    feature = "serde",
568    serde(bound(
569        serialize = "C::AffinePoint: serde::Serialize, C::Scalar: serde::Serialize",
570        deserialize = "C::AffinePoint: serde::Deserialize<'de>, C::Scalar: serde::Deserialize<'de>"
571    ))
572)]
573pub struct CPProof<C: DklsCurve> {
574    pub base_g: C::AffinePoint, // Parameters for the proof.
575    pub base_h: C::AffinePoint, // In the encryption proof, base_g = generator.
576    pub point_u: C::AffinePoint,
577    pub point_v: C::AffinePoint,
578
579    pub challenge_response: C::Scalar,
580}
581
582impl<C: DklsCurve> CPProof<C> {
583    // We need a proof that scalar * base_g = point_u and scalar * base_h = point_v.
584    // As we will see later, the challenge will not be calculated only with the data
585    // we now have. Thus, we have to write the interactive version here for the moment.
586    // This means that the challenge is a parameter chosen by the verifier and is not
587    // calculated via Fiat-Shamir.
588
589    /// Step 1 - Samples the random commitments.
590    ///
591    /// The `Scalar` is kept secret while the `RandomCommitments` is transmitted.
592    #[must_use]
593    pub fn prove_step1(
594        base_g: &C::AffinePoint,
595        base_h: &C::AffinePoint,
596    ) -> (C::Scalar, RandomCommitments<C>) {
597        // We sample a nonzero random scalar.
598        let mut scalar_rand_commitment = <C::Scalar as Field>::ZERO;
599        while scalar_rand_commitment == <C::Scalar as Field>::ZERO {
600            scalar_rand_commitment = <C::Scalar as Field>::random(&mut rng::get_rng());
601        }
602
603        let point_rand_commitment_g = (*base_g * scalar_rand_commitment).to_affine();
604        let point_rand_commitment_h = (*base_h * scalar_rand_commitment).to_affine();
605
606        let rand_commitments = RandomCommitments {
607            rc_g: point_rand_commitment_g,
608            rc_h: point_rand_commitment_h,
609        };
610
611        (scalar_rand_commitment, rand_commitments)
612    }
613
614    /// Step 2 - Compute the response for a given challenge.
615    ///
616    /// Here, `scalar` is the witness for the proof and `scalar_rand_commitment`
617    /// is the secret value from the previous step.
618    #[must_use]
619    pub fn prove_step2(
620        base_g: &C::AffinePoint,
621        base_h: &C::AffinePoint,
622        scalar: &C::Scalar,
623        scalar_rand_commitment: &C::Scalar,
624        challenge: &C::Scalar,
625    ) -> CPProof<C> {
626        // We get u and v.
627        let point_u = (*base_g * scalar).to_affine();
628        let point_v = (*base_h * scalar).to_affine();
629
630        // We compute the response.
631        let challenge_response = *scalar_rand_commitment - (*challenge * scalar);
632
633        CPProof {
634            base_g: *base_g,
635            base_h: *base_h,
636            point_u,
637            point_v,
638
639            challenge_response,
640        }
641    }
642
643    /// Verification of a proof.
644    ///
645    /// Note that the data to be verified is in the variable `proof`.
646    ///
647    /// The verifier must know the challenge (in this interactive version, he chooses it).
648    #[must_use]
649    pub fn verify(&self, rand_commitments: &RandomCommitments<C>, challenge: &C::Scalar) -> bool {
650        // We compare the values that should agree.
651        let point_verify_g =
652            ((self.base_g * self.challenge_response) + (self.point_u * challenge)).to_affine();
653        let point_verify_h =
654            ((self.base_h * self.challenge_response) + (self.point_v * challenge)).to_affine();
655
656        (point_verify_g == rand_commitments.rc_g) && (point_verify_h == rand_commitments.rc_h)
657    }
658
659    /// Simulates a "fake" proof which passes the `verify` method.
660    ///
661    /// To do so, the prover samples the challenge and uses it to compute
662    /// the other values. This method returns the challenge used, the commitments
663    /// and the corresponding proof.
664    ///
665    /// This is needed during the OR-composition protocol (see [`EncProof`]).
666    #[must_use]
667    pub fn simulate(
668        base_g: &C::AffinePoint,
669        base_h: &C::AffinePoint,
670        point_u: &C::AffinePoint,
671        point_v: &C::AffinePoint,
672    ) -> (RandomCommitments<C>, C::Scalar, CPProof<C>) {
673        // We sample the challenge and the response first.
674        let challenge = <C::Scalar as Field>::random(&mut rng::get_rng());
675
676        let challenge_response = <C::Scalar as Field>::random(&mut rng::get_rng());
677
678        // Now we compute the "random" commitments that work for this challenge.
679        let point_rand_commitment_g =
680            ((*base_g * challenge_response) + (*point_u * challenge)).to_affine();
681        let point_rand_commitment_h =
682            ((*base_h * challenge_response) + (*point_v * challenge)).to_affine();
683
684        let rand_commitments = RandomCommitments {
685            rc_g: point_rand_commitment_g,
686            rc_h: point_rand_commitment_h,
687        };
688
689        let proof = CPProof {
690            base_g: *base_g,
691            base_h: *base_h,
692            point_u: *point_u,
693            point_v: *point_v,
694
695            challenge_response,
696        };
697
698        (rand_commitments, challenge, proof)
699    }
700}
701
702/// Encryption proof used during the Endemic OT protocol of Zhou et al.
703///
704/// See page 17 of <https://eprint.iacr.org/2022/1525.pdf>.
705#[derive(Debug, Clone)]
706#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
707#[cfg_attr(
708    feature = "serde",
709    serde(bound(
710        serialize = "C::AffinePoint: serde::Serialize, C::Scalar: serde::Serialize",
711        deserialize = "C::AffinePoint: serde::Deserialize<'de>, C::Scalar: serde::Deserialize<'de>"
712    ))
713)]
714pub struct EncProof<C: DklsCurve> {
715    /// EncProof is a proof that `proof0` or `proof1` really proves what it says.
716    pub proof0: CPProof<C>,
717    pub proof1: CPProof<C>,
718
719    pub commitments0: RandomCommitments<C>,
720    pub commitments1: RandomCommitments<C>,
721
722    pub challenge0: C::Scalar,
723    pub challenge1: C::Scalar,
724}
725
726impl<C: DklsCurve> EncProof<C> {
727    /// Computes a proof for the witness `scalar`.
728    ///
729    /// The variable `bit` indicates which one of the proofs is really
730    /// proved by `scalar`. The other one is simulated.
731    #[must_use]
732    pub fn prove(
733        session_id: &[u8],
734        base_h: &C::AffinePoint,
735        scalar: &C::Scalar,
736        bit: bool,
737    ) -> EncProof<C> {
738        // PRELIMINARIES
739
740        // g is the generator in this case.
741        let base_g = <C::AffinePoint as PrimeCurveAffine>::generator();
742
743        // We compute u and v from Section 3 in the paper.
744        // Be careful: these are not point_u and point_v from CPProof.
745
746        // u is independent of the bit chosen.
747        let u = (*base_h * scalar).to_affine();
748
749        // v = h*bit + g*scalar.
750        // The other possible value for v will be used in a simulated proof.
751        // See below for a better explanation.
752        //
753        // Both branches are computed unconditionally to avoid timing
754        // side-channels that could leak the OT choice bit.
755        let base_h_proj = C::ProjectivePoint::from(*base_h);
756        let g_times_scalar = base_g * scalar;
757        let v_if_true = (g_times_scalar + base_h_proj).to_affine();
758        let v_if_false = g_times_scalar.to_affine();
759        let fake_v_if_true = v_if_true;
760        let fake_v_if_false = (g_times_scalar - base_h_proj).to_affine();
761
762        let (v, fake_v) = if bit {
763            (v_if_true, fake_v_if_true)
764        } else {
765            (v_if_false, fake_v_if_false)
766        };
767
768        // STEP 1
769        // We start our real proof and simulate the fake proof.
770
771        // Real proof:
772        // bit = 0 => We want to prove that (g,h,v,u) is a DDH tuple.
773        // bit = 1 => We want to prove that (g,h,v-h,u) is a DDH tuple.
774
775        // Fake proof: Simulate that (g,h,fake_v,u) is a DDH tuple (although it's not).
776        // bit = 0 => We want to fake that (g,h,v-h,u) is a DDH tuple (i.e., fake_v = v-h).
777        // bit = 1 -> We want to fake that (g,h,v,u) is a DDH tuple (i.e., fake_v = v).
778
779        // Commitments for real proof.
780        let (real_scalar_commitment, real_commitments) = CPProof::prove_step1(&base_g, base_h);
781
782        // Fake proof.
783        let (fake_commitments, fake_challenge, fake_proof) =
784            CPProof::simulate(&base_g, base_h, &fake_v, &u);
785
786        // STEP 2
787        // Fiat-Shamir: We compute the "total" challenge based on the
788        // values we want to prove and on the commitments above.
789
790        let base_g_as_bytes = point_to_bytes::<C>(&base_g);
791        let base_h_as_bytes = point_to_bytes::<C>(base_h);
792        let u_as_bytes = point_to_bytes::<C>(&u);
793        let v_as_bytes = point_to_bytes::<C>(&v);
794
795        let r_rc_g_as_bytes = point_to_bytes::<C>(&real_commitments.rc_g);
796        let r_rc_h_as_bytes = point_to_bytes::<C>(&real_commitments.rc_h);
797
798        let f_rc_g_as_bytes = point_to_bytes::<C>(&fake_commitments.rc_g);
799        let f_rc_h_as_bytes = point_to_bytes::<C>(&fake_commitments.rc_h);
800
801        // The proof that comes first is always the one containing u and v.
802        // If bit = 0, it is the real proof, otherwise it is the fake one.
803        // For the message, we first put the commitments for the first proof
804        // since the verifier does not know which proof is the real one.
805        let msg_for_challenge = if bit {
806            [
807                base_g_as_bytes,
808                base_h_as_bytes,
809                u_as_bytes,
810                v_as_bytes,
811                f_rc_g_as_bytes,
812                f_rc_h_as_bytes,
813                r_rc_g_as_bytes,
814                r_rc_h_as_bytes,
815            ]
816            .concat()
817        } else {
818            [
819                base_g_as_bytes,
820                base_h_as_bytes,
821                u_as_bytes,
822                v_as_bytes,
823                r_rc_g_as_bytes,
824                r_rc_h_as_bytes,
825                f_rc_g_as_bytes,
826                f_rc_h_as_bytes,
827            ]
828            .concat()
829        };
830
831        let challenge =
832            tagged_hash_as_scalar::<C>(TAG_ENCPROOF_FS, &[session_id, &msg_for_challenge]);
833
834        // STEP 3
835        // We compute the real challenge for our real proof.
836        // Note that it depends on the challenge above. This
837        // is why we cannot simply fake both proofs. With this
838        // challenge, we can finish the real proof.
839
840        // ATTENTION: The original paper says that the challenge
841        // should be the XOR of the real and fake challenges.
842        // However, it is easier and essentially equivalent to
843        // impose that challenge = real + fake as scalars.
844
845        let real_challenge = challenge - fake_challenge;
846
847        let real_proof = CPProof::prove_step2(
848            &base_g,
849            base_h,
850            scalar,
851            &real_scalar_commitment,
852            &real_challenge,
853        );
854
855        // RETURN
856
857        // The proof containing u and v goes first.
858        // It is the real proof if bit = 0 and the false one otherwise.
859        if bit {
860            EncProof {
861                proof0: fake_proof,
862                proof1: real_proof,
863
864                commitments0: fake_commitments,
865                commitments1: real_commitments,
866
867                challenge0: fake_challenge,
868                challenge1: real_challenge,
869            }
870        } else {
871            EncProof {
872                proof0: real_proof,
873                proof1: fake_proof,
874
875                commitments0: real_commitments,
876                commitments1: fake_commitments,
877
878                challenge0: real_challenge,
879                challenge1: fake_challenge,
880            }
881        }
882    }
883
884    /// Verification of an encryption proof.
885    ///
886    /// Note that the data to be verified is in `proof`.
887    #[must_use]
888    pub fn verify(&self, session_id: &[u8]) -> bool {
889        // We check if the proofs are compatible.
890        let generator = <C::AffinePoint as PrimeCurveAffine>::generator();
891        if (self.proof0.base_g != generator)
892        || (self.proof0.base_g != self.proof1.base_g)
893        || (self.proof0.base_h != self.proof1.base_h)
894        || (self.proof0.point_v != self.proof1.point_v) // This is u from Section 3 in the paper.
895        || (self.proof0.point_u != (C::ProjectivePoint::from(self.proof1.point_u) + C::ProjectivePoint::from(self.proof1.base_h)).to_affine())
896        // proof0 contains v and proof1 contains v-h.
897        {
898            return false;
899        }
900
901        // Reconstructing the challenge.
902
903        let base_g_as_bytes = point_to_bytes::<C>(&self.proof0.base_g);
904        let base_h_as_bytes = point_to_bytes::<C>(&self.proof0.base_h);
905
906        // u and v are respectively point_v and point_u from the proof0.
907        let u_as_bytes = point_to_bytes::<C>(&self.proof0.point_v);
908        let v_as_bytes = point_to_bytes::<C>(&self.proof0.point_u);
909
910        let rc0_g_as_bytes = point_to_bytes::<C>(&self.commitments0.rc_g);
911        let rc0_h_as_bytes = point_to_bytes::<C>(&self.commitments0.rc_h);
912
913        let rc1_g_as_bytes = point_to_bytes::<C>(&self.commitments1.rc_g);
914        let rc1_h_as_bytes = point_to_bytes::<C>(&self.commitments1.rc_h);
915
916        let msg_for_challenge = [
917            base_g_as_bytes,
918            base_h_as_bytes,
919            u_as_bytes,
920            v_as_bytes,
921            rc0_g_as_bytes,
922            rc0_h_as_bytes,
923            rc1_g_as_bytes,
924            rc1_h_as_bytes,
925        ]
926        .concat();
927        let expected_challenge =
928            tagged_hash_as_scalar::<C>(TAG_ENCPROOF_FS, &[session_id, &msg_for_challenge]);
929
930        // The challenge should be the sum of the challenges used in the proofs.
931        if expected_challenge != self.challenge0 + self.challenge1 {
932            return false;
933        }
934
935        // Finally, we check if both proofs are valid.
936        self.proof0.verify(&self.commitments0, &self.challenge0)
937            && self.proof1.verify(&self.commitments1, &self.challenge1)
938    }
939
940    /// Extracts `u` and `v` from an instance of `EncProof`.
941    ///
942    /// Be careful: the notation for `u` and `v` here is the
943    /// same as the one used in the paper by Zhou et al. at page 17.
944    /// Unfortunately, `u` and `v` appear in the other order in
945    /// their description of the Chaum-Pedersen protocol.
946    /// Hence, `u` and `v` here are not the same as `point_u`
947    /// and `point_v` in [`CPProof`].
948    #[must_use]
949    pub fn get_u_and_v(&self) -> (C::AffinePoint, C::AffinePoint) {
950        (self.proof0.point_v, self.proof0.point_u)
951    }
952}
953
954#[cfg(test)]
955mod tests {
956    use super::*;
957    use k256::Secp256k1;
958
959    type TestCurve = Secp256k1;
960    type Scalar = <TestCurve as elliptic_curve::CurveArithmetic>::Scalar;
961    type AffinePoint = <TestCurve as elliptic_curve::CurveArithmetic>::AffinePoint;
962    type ProjectivePoint = <TestCurve as elliptic_curve::CurveArithmetic>::ProjectivePoint;
963
964    // DLogProof
965
966    /// Tests if proving and verifying work for [`DLogProof`].
967    #[test]
968    fn test_dlog_proof() {
969        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
970        let session_id = rng::get_rng().random::<[u8; 32]>();
971        let proof = DLogProof::<TestCurve>::prove(&scalar, &session_id).unwrap();
972        assert!(DLogProof::<TestCurve>::verify(&proof, &session_id));
973    }
974
975    /// Generates a [`DLogProof`] and changes it on purpose
976    /// to see if the verify function detects.
977    #[test]
978    fn test_dlog_proof_fail_proof() {
979        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
980        let session_id = rng::get_rng().random::<[u8; 32]>();
981        let mut proof = DLogProof::<TestCurve>::prove(&scalar, &session_id).unwrap();
982        proof.proofs[0].challenge_response *= Scalar::from(2u32); //Changing the proof
983        assert!(!(DLogProof::<TestCurve>::verify(&proof, &session_id)));
984    }
985
986    /// Ensures duplicated random commitments are rejected.
987    #[test]
988    fn test_dlog_proof_rejects_duplicate_rand_commitments() {
989        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
990        let session_id = rng::get_rng().random::<[u8; 32]>();
991        let mut proof = DLogProof::<TestCurve>::prove(&scalar, &session_id).unwrap();
992        proof.rand_commitments[1] = proof.rand_commitments[0];
993        assert!(!DLogProof::<TestCurve>::verify(&proof, &session_id));
994    }
995
996    /// Ensures wrong proof/commitment vector lengths are rejected.
997    #[test]
998    fn test_dlog_proof_rejects_wrong_vector_lengths() {
999        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1000        let session_id = rng::get_rng().random::<[u8; 32]>();
1001
1002        let mut proof_short_commitments =
1003            DLogProof::<TestCurve>::prove(&scalar, &session_id).unwrap();
1004        proof_short_commitments.rand_commitments.pop();
1005        assert!(!DLogProof::<TestCurve>::verify(
1006            &proof_short_commitments,
1007            &session_id
1008        ));
1009
1010        let mut proof_short_proofs = DLogProof::<TestCurve>::prove(&scalar, &session_id).unwrap();
1011        proof_short_proofs.proofs.pop();
1012        assert!(!DLogProof::<TestCurve>::verify(
1013            &proof_short_proofs,
1014            &session_id
1015        ));
1016    }
1017
1018    /// Ensures session-id mismatches invalidate the proof.
1019    #[test]
1020    fn test_dlog_proof_rejects_mismatched_session_id() {
1021        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1022        let prove_sid = rng::get_rng().random::<[u8; 32]>();
1023        let mut verify_sid = prove_sid;
1024        verify_sid[0] ^= 1;
1025        let proof = DLogProof::<TestCurve>::prove(&scalar, &prove_sid).unwrap();
1026        assert!(!DLogProof::<TestCurve>::verify(&proof, &verify_sid));
1027    }
1028
1029    /// Tests if proving and verifying work for [`DLogProof`]
1030    /// in the case with commitment.
1031    #[test]
1032    fn test_dlog_proof_commit() {
1033        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1034        let session_id = rng::get_rng().random::<[u8; 32]>();
1035        let (proof, commitment) =
1036            DLogProof::<TestCurve>::prove_commit(&scalar, &session_id).unwrap();
1037        assert!(DLogProof::<TestCurve>::decommit_verify(
1038            &proof,
1039            &commitment,
1040            &session_id
1041        ));
1042    }
1043
1044    /// Generates a [`DLogProof`] with commitment and changes
1045    /// the proof on purpose to see if the verify function detects.
1046    #[test]
1047    fn test_dlog_proof_commit_fail_proof() {
1048        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1049        let session_id = rng::get_rng().random::<[u8; 32]>();
1050        let (mut proof, commitment) =
1051            DLogProof::<TestCurve>::prove_commit(&scalar, &session_id).unwrap();
1052        proof.proofs[0].challenge_response *= Scalar::from(2u32); //Changing the proof
1053        assert!(!(DLogProof::<TestCurve>::decommit_verify(&proof, &commitment, &session_id)));
1054    }
1055
1056    /// Generates a [`DLogProof`] with commitment and changes
1057    /// the commitment on purpose to see if the verify function detects.
1058    #[test]
1059    fn test_dlog_proof_commit_fail_commitment() {
1060        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1061        let session_id = rng::get_rng().random::<[u8; 32]>();
1062        let (proof, mut commitment) =
1063            DLogProof::<TestCurve>::prove_commit(&scalar, &session_id).unwrap();
1064        if commitment[0] == 0 {
1065            commitment[0] = 1;
1066        } else {
1067            commitment[0] -= 1;
1068        } //Changing the commitment
1069        assert!(!(DLogProof::<TestCurve>::decommit_verify(&proof, &commitment, &session_id)));
1070    }
1071
1072    // CPProof
1073
1074    /// Tests if proving and verifying work for [`CPProof`].
1075    #[test]
1076    fn test_cp_proof() {
1077        let log_base_g = <Scalar as Field>::random(&mut rng::get_rng());
1078        let log_base_h = <Scalar as Field>::random(&mut rng::get_rng());
1079        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1080
1081        let generator = <AffinePoint as PrimeCurveAffine>::generator();
1082        let base_g = (generator * log_base_g).to_affine();
1083        let base_h = (generator * log_base_h).to_affine();
1084
1085        // Prover - Step 1.
1086        let (scalar_rand_commitment, rand_commitments) =
1087            CPProof::<TestCurve>::prove_step1(&base_g, &base_h);
1088
1089        // Verifier - Gather the commitments and choose the challenge.
1090        let challenge = <Scalar as Field>::random(&mut rng::get_rng());
1091
1092        // Prover - Step 2.
1093        let proof = CPProof::<TestCurve>::prove_step2(
1094            &base_g,
1095            &base_h,
1096            &scalar,
1097            &scalar_rand_commitment,
1098            &challenge,
1099        );
1100
1101        // Verifier verifies the proof.
1102        let verification = proof.verify(&rand_commitments, &challenge);
1103
1104        assert!(verification);
1105    }
1106
1107    /// Tests if simulating a fake proof and verifying work for [`CPProof`].
1108    #[test]
1109    fn test_cp_proof_simulate() {
1110        let log_base_g = <Scalar as Field>::random(&mut rng::get_rng());
1111        let log_base_h = <Scalar as Field>::random(&mut rng::get_rng());
1112        let log_point_u = <Scalar as Field>::random(&mut rng::get_rng());
1113        let log_point_v = <Scalar as Field>::random(&mut rng::get_rng());
1114
1115        let generator = <AffinePoint as PrimeCurveAffine>::generator();
1116        let base_g = (generator * log_base_g).to_affine();
1117        let base_h = (generator * log_base_h).to_affine();
1118        let point_u = (generator * log_point_u).to_affine();
1119        let point_v = (generator * log_point_v).to_affine();
1120
1121        // Simulation.
1122        let (rand_commitments, challenge, proof) =
1123            CPProof::<TestCurve>::simulate(&base_g, &base_h, &point_u, &point_v);
1124
1125        let verification = proof.verify(&rand_commitments, &challenge);
1126
1127        assert!(verification);
1128    }
1129
1130    /// Ensures simulated proofs fail when verified against a different statement.
1131    #[test]
1132    fn test_cp_proof_simulate_wrong_statement_fails() {
1133        let log_base_g = <Scalar as Field>::random(&mut rng::get_rng());
1134        let log_base_h = <Scalar as Field>::random(&mut rng::get_rng());
1135        let log_point_u = <Scalar as Field>::random(&mut rng::get_rng());
1136        let log_point_v = <Scalar as Field>::random(&mut rng::get_rng());
1137
1138        let generator = <AffinePoint as PrimeCurveAffine>::generator();
1139        let base_g = (generator * log_base_g).to_affine();
1140        let base_h = (generator * log_base_h).to_affine();
1141        let point_u = (generator * log_point_u).to_affine();
1142        let point_v = (generator * log_point_v).to_affine();
1143
1144        // We intentionally use a random statement and then tamper it further.
1145        let (rand_commitments, challenge, mut proof) =
1146            CPProof::<TestCurve>::simulate(&base_g, &base_h, &point_u, &point_v);
1147        proof.point_u =
1148            (ProjectivePoint::from(proof.point_u) + ProjectivePoint::GENERATOR).to_affine();
1149
1150        assert!(!proof.verify(&rand_commitments, &challenge));
1151    }
1152
1153    // EncProof
1154
1155    /// Tests if proving and verifying work for [`EncProof`].
1156    #[test]
1157    fn test_enc_proof() {
1158        // We sample the initial values.
1159        let session_id = rng::get_rng().random::<[u8; 32]>();
1160
1161        let log_base_h = <Scalar as Field>::random(&mut rng::get_rng());
1162        let generator = <AffinePoint as PrimeCurveAffine>::generator();
1163        let base_h = (generator * log_base_h).to_affine();
1164
1165        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1166
1167        let bit: bool = rng::get_rng().random();
1168
1169        // Proving.
1170        let proof = EncProof::<TestCurve>::prove(&session_id, &base_h, &scalar, bit);
1171
1172        // Verifying.
1173        let verification = proof.verify(&session_id);
1174
1175        assert!(verification);
1176    }
1177
1178    /// Ensures incompatible sub-proofs are rejected.
1179    #[test]
1180    fn test_enc_proof_rejects_incompatible_subproofs() {
1181        let session_id = rng::get_rng().random::<[u8; 32]>();
1182        let log_base_h = <Scalar as Field>::random(&mut rng::get_rng());
1183        let generator = <AffinePoint as PrimeCurveAffine>::generator();
1184        let base_h = (generator * log_base_h).to_affine();
1185        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1186        let bit: bool = rng::get_rng().random();
1187
1188        let mut proof = EncProof::<TestCurve>::prove(&session_id, &base_h, &scalar, bit);
1189        proof.proof0.base_g = (generator * Scalar::from(2u32)).to_affine();
1190
1191        assert!(!proof.verify(&session_id));
1192    }
1193
1194    /// Ensures challenge-sum mismatch is rejected.
1195    #[test]
1196    fn test_enc_proof_rejects_challenge_sum_mismatch() {
1197        let session_id = rng::get_rng().random::<[u8; 32]>();
1198        let log_base_h = <Scalar as Field>::random(&mut rng::get_rng());
1199        let generator = <AffinePoint as PrimeCurveAffine>::generator();
1200        let base_h = (generator * log_base_h).to_affine();
1201        let scalar = <Scalar as Field>::random(&mut rng::get_rng());
1202        let bit: bool = rng::get_rng().random();
1203
1204        let mut proof = EncProof::<TestCurve>::prove(&session_id, &base_h, &scalar, bit);
1205        proof.challenge0 += Scalar::ONE;
1206
1207        assert!(!proof.verify(&session_id));
1208    }
1209
1210    /// Tests that oversized interactive challenges are rejected during verification.
1211    #[test]
1212    fn test_interactive_dlog_proof_rejects_oversized_challenge() {
1213        let generator = <AffinePoint as PrimeCurveAffine>::generator();
1214        let proof: InteractiveDLogProof<TestCurve> = InteractiveDLogProof {
1215            challenge: vec![0u8; (T / 8 + 1) as usize],
1216            challenge_response: <Scalar as Field>::ZERO,
1217            _curve: PhantomData,
1218        };
1219        assert!(!proof.verify(&generator, &generator));
1220    }
1221
1222    /// Tests that empty challenges are rejected during verification.
1223    #[test]
1224    fn test_interactive_dlog_proof_rejects_empty_challenge() {
1225        let generator = <AffinePoint as PrimeCurveAffine>::generator();
1226        let proof: InteractiveDLogProof<TestCurve> = InteractiveDLogProof {
1227            challenge: vec![],
1228            challenge_response: <Scalar as Field>::ZERO,
1229            _curve: PhantomData,
1230        };
1231        assert!(!proof.verify(&generator, &generator));
1232    }
1233}