elastic_elgamal/proofs/
possession.rs

1//! [`ProofOfPossession`] and related logic.
2
3use merlin::Transcript;
4use rand_core::{CryptoRng, RngCore};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "serde")]
9use crate::serde::{ScalarHelper, VecHelper};
10use crate::{
11    alloc::Vec,
12    group::Group,
13    proofs::{TranscriptForGroup, VerificationError},
14    Keypair, PublicKey, SecretKey,
15};
16
17/// Zero-knowledge proof of possession of one or more secret scalars.
18///
19/// # Construction
20///
21/// The proof is a generalization of the standard Schnorr protocol for proving knowledge
22/// of a discrete log. The difference with the combination of several concurrent Schnorr
23/// protocol instances is that the challenge is shared among all instances (which yields a
24/// ~2x proof size reduction).
25///
26/// # Implementation notes
27///
28/// - Proof generation is constant-time. Verification is **not** constant-time.
29///
30/// # Examples
31///
32/// ```
33/// # use elastic_elgamal::{group::Ristretto, Keypair, ProofOfPossession};
34/// # use merlin::Transcript;
35/// # use rand::thread_rng;
36/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
37/// let mut rng = thread_rng();
38/// let keypairs: Vec<_> =
39///     (0..5).map(|_| Keypair::<Ristretto>::generate(&mut rng)).collect();
40///
41/// // Prove possession of the generated key pairs.
42/// let proof = ProofOfPossession::new(
43///     &keypairs,
44///     &mut Transcript::new(b"custom_proof"),
45///     &mut rng,
46/// );
47/// proof.verify(
48///     keypairs.iter().map(Keypair::public),
49///     &mut Transcript::new(b"custom_proof"),
50/// )?;
51///
52/// // If we change the context of the `Transcript`, the proof will not verify.
53/// assert!(proof
54///     .verify(
55///         keypairs.iter().map(Keypair::public),
56///         &mut Transcript::new(b"other_proof"),
57///     )
58///     .is_err());
59/// // Likewise if the public keys are reordered.
60/// assert!(proof
61///     .verify(
62///         keypairs.iter().rev().map(Keypair::public),
63///         &mut Transcript::new(b"custom_proof"),
64///     )
65///     .is_err());
66/// # Ok(())
67/// # }
68/// ```
69#[derive(Debug, Clone)]
70#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
71#[cfg_attr(feature = "serde", serde(bound = ""))]
72pub struct ProofOfPossession<G: Group> {
73    #[cfg_attr(feature = "serde", serde(with = "ScalarHelper::<G>"))]
74    challenge: G::Scalar,
75    #[cfg_attr(feature = "serde", serde(with = "VecHelper::<ScalarHelper<G>, 1>"))]
76    responses: Vec<G::Scalar>,
77}
78
79impl<G: Group> ProofOfPossession<G> {
80    /// Creates a proof of possession with the specified `keypairs`.
81    pub fn new<R: CryptoRng + RngCore>(
82        keypairs: &[Keypair<G>],
83        transcript: &mut Transcript,
84        rng: &mut R,
85    ) -> Self {
86        Self::from_keys(
87            keypairs.iter().map(Keypair::secret),
88            keypairs.iter().map(Keypair::public),
89            transcript,
90            rng,
91        )
92    }
93
94    pub(crate) fn from_keys<'a, R: CryptoRng + RngCore>(
95        secrets: impl Iterator<Item = &'a SecretKey<G>>,
96        public_keys: impl Iterator<Item = &'a PublicKey<G>>,
97        transcript: &mut Transcript,
98        rng: &mut R,
99    ) -> Self {
100        transcript.start_proof(b"multi_pop");
101        let mut key_count = 0;
102        for public_key in public_keys {
103            transcript.append_element_bytes(b"K", public_key.as_bytes());
104            key_count += 1;
105        }
106
107        let random_scalars: Vec<_> = (0..key_count)
108            .map(|_| {
109                let randomness = SecretKey::<G>::generate(rng);
110                let random_element = G::mul_generator(randomness.expose_scalar());
111                transcript.append_element::<G>(b"R", &random_element);
112                randomness
113            })
114            .collect();
115
116        let challenge = transcript.challenge_scalar::<G>(b"c");
117        let responses = secrets
118            .zip(random_scalars)
119            .map(|(log, mut randomness)| {
120                randomness += log * &challenge;
121                *randomness.expose_scalar()
122            })
123            .collect();
124
125        Self {
126            challenge,
127            responses,
128        }
129    }
130
131    /// Verifies this proof against the provided `public_keys`.
132    ///
133    /// # Errors
134    ///
135    /// Returns an error if this proof does not verify.
136    pub fn verify<'a>(
137        &self,
138        public_keys: impl Iterator<Item = &'a PublicKey<G>> + Clone,
139        transcript: &mut Transcript,
140    ) -> Result<(), VerificationError> {
141        let mut key_count = 0;
142        transcript.start_proof(b"multi_pop");
143        for public_key in public_keys.clone() {
144            transcript.append_element_bytes(b"K", public_key.as_bytes());
145            key_count += 1;
146        }
147        VerificationError::check_lengths("public keys", self.responses.len(), key_count)?;
148
149        for (public_key, response) in public_keys.zip(&self.responses) {
150            let random_element = G::vartime_double_mul_generator(
151                &-self.challenge,
152                public_key.as_element(),
153                response,
154            );
155            transcript.append_element::<G>(b"R", &random_element);
156        }
157
158        let expected_challenge = transcript.challenge_scalar::<G>(b"c");
159        if expected_challenge == self.challenge {
160            Ok(())
161        } else {
162            Err(VerificationError::ChallengeMismatch)
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use rand::thread_rng;
170
171    use super::*;
172    use crate::group::Ristretto;
173
174    type Keypair = crate::Keypair<Ristretto>;
175
176    #[test]
177    fn proof_of_possession_basics() {
178        let mut rng = thread_rng();
179        let poly: Vec<_> = (0..5).map(|_| Keypair::generate(&mut rng)).collect();
180
181        ProofOfPossession::new(&poly, &mut Transcript::new(b"test_multi_PoP"), &mut rng)
182            .verify(
183                poly.iter().map(Keypair::public),
184                &mut Transcript::new(b"test_multi_PoP"),
185            )
186            .unwrap();
187    }
188}