challenge_bypass_ristretto/
dleq.rs

1#[cfg(all(feature = "alloc", not(feature = "std")))]
2use alloc::vec::Vec;
3
4#[cfg(feature = "std")]
5use std::vec::Vec;
6
7use core::iter;
8
9use curve25519_dalek::constants;
10use curve25519_dalek::ristretto::RistrettoPoint;
11use curve25519_dalek::scalar::Scalar;
12use curve25519_dalek::traits::VartimeMultiscalarMul;
13use digest::generic_array::typenum::U64;
14use digest::Digest;
15use rand::{CryptoRng, Rng, SeedableRng};
16use rand_chacha::ChaChaRng;
17
18use crate::errors::{InternalError, TokenError};
19use crate::oprf::*;
20
21/// The length of a `DLEQProof`, in bytes.
22pub const DLEQ_PROOF_LENGTH: usize = 64;
23
24/// A `DLEQProof` is a proof of the equivalence of the discrete logarithm between two pairs of points.
25#[allow(non_snake_case)]
26#[derive(Debug, Clone)]
27pub struct DLEQProof {
28    /// `c` is a `Scalar`
29    /// \\(c=H_3(X,Y,P,Q,A,B)\\)
30    pub(crate) c: Scalar,
31    /// `s` is a `Scalar`
32    /// \\(s = (t - ck) \mod q\\)
33    pub(crate) s: Scalar,
34}
35
36#[cfg(any(test, feature = "base64"))]
37impl_base64!(DLEQProof);
38
39#[cfg(feature = "serde")]
40impl_serde!(DLEQProof);
41
42#[allow(non_snake_case)]
43impl DLEQProof {
44    /// Construct a new `DLEQProof`
45    fn _new<D, T>(rng: &mut T, P: RistrettoPoint, Q: RistrettoPoint, k: &SigningKey) -> Self
46    where
47        D: Digest<OutputSize = U64> + Default,
48        T: Rng + CryptoRng,
49    {
50        let t = Scalar::random(rng);
51
52        let A = t * constants::RISTRETTO_BASEPOINT_POINT;
53        let B = t * P;
54
55        let mut h = D::default();
56
57        let X = constants::RISTRETTO_BASEPOINT_COMPRESSED;
58        let Y = k.public_key.0;
59        let P = P.compress();
60        let Q = Q.compress();
61        let A = A.compress();
62        let B = B.compress();
63
64        h.update(X.as_bytes());
65        h.update(Y.as_bytes());
66        h.update(P.as_bytes());
67        h.update(Q.as_bytes());
68        h.update(A.as_bytes());
69        h.update(B.as_bytes());
70
71        let c = Scalar::from_hash(h);
72
73        let s = t - c * k.k;
74
75        DLEQProof { c, s }
76    }
77
78    /// Construct a new `DLEQProof`
79    pub fn new<D, T>(
80        rng: &mut T,
81        blinded_token: &BlindedToken,
82        signed_token: &SignedToken,
83        k: &SigningKey,
84    ) -> Result<Self, TokenError>
85    where
86        D: Digest<OutputSize = U64> + Default,
87        T: Rng + CryptoRng,
88    {
89        Ok(Self::_new::<D, T>(
90            rng,
91            blinded_token
92                .0
93                .decompress()
94                .ok_or(TokenError(InternalError::PointDecompressionError))?,
95            signed_token
96                .0
97                .decompress()
98                .ok_or(TokenError(InternalError::PointDecompressionError))?,
99            k,
100        ))
101    }
102
103    /// Verify the `DLEQProof`
104    fn _verify<D>(
105        &self,
106        P: RistrettoPoint,
107        Q: RistrettoPoint,
108        public_key: &PublicKey,
109    ) -> Result<(), TokenError>
110    where
111        D: Digest<OutputSize = U64> + Default,
112    {
113        let X = constants::RISTRETTO_BASEPOINT_COMPRESSED;
114        let Y = public_key.0;
115
116        let A = (self.s * constants::RISTRETTO_BASEPOINT_POINT)
117            + (self.c
118                * Y.decompress()
119                    .ok_or(TokenError(InternalError::PointDecompressionError))?);
120        let B = (self.s * P) + (self.c * Q);
121
122        let A = A.compress();
123        let B = B.compress();
124        let P = P.compress();
125        let Q = Q.compress();
126
127        let mut h = D::default();
128
129        h.update(X.as_bytes());
130        h.update(Y.as_bytes());
131        h.update(P.as_bytes());
132        h.update(Q.as_bytes());
133        h.update(A.as_bytes());
134        h.update(B.as_bytes());
135
136        let c = Scalar::from_hash(h);
137
138        if c == self.c {
139            Ok(())
140        } else {
141            Err(TokenError(InternalError::VerifyError))
142        }
143    }
144
145    /// Verify the `DLEQProof`
146    pub fn verify<D>(
147        &self,
148        blinded_token: &BlindedToken,
149        signed_token: &SignedToken,
150        public_key: &PublicKey,
151    ) -> Result<(), TokenError>
152    where
153        D: Digest<OutputSize = U64> + Default,
154    {
155        self._verify::<D>(
156            blinded_token
157                .0
158                .decompress()
159                .ok_or(TokenError(InternalError::PointDecompressionError))?,
160            signed_token
161                .0
162                .decompress()
163                .ok_or(TokenError(InternalError::PointDecompressionError))?,
164            public_key,
165        )
166    }
167}
168
169impl DLEQProof {
170    /// Convert this `DLEQProof` to a byte array.
171    pub fn to_bytes(&self) -> [u8; DLEQ_PROOF_LENGTH] {
172        let mut proof_bytes: [u8; DLEQ_PROOF_LENGTH] = [0u8; DLEQ_PROOF_LENGTH];
173
174        proof_bytes[..32].copy_from_slice(&self.c.to_bytes());
175        proof_bytes[32..].copy_from_slice(&self.s.to_bytes());
176        proof_bytes
177    }
178
179    fn bytes_length_error() -> TokenError {
180        TokenError(InternalError::BytesLengthError {
181            name: "DLEQProof",
182            length: DLEQ_PROOF_LENGTH,
183        })
184    }
185
186    /// Construct a `DLEQProof` from a slice of bytes.
187    pub fn from_bytes(bytes: &[u8]) -> Result<DLEQProof, TokenError> {
188        if bytes.len() != DLEQ_PROOF_LENGTH {
189            return Err(DLEQProof::bytes_length_error());
190        }
191
192        let mut c_bits: [u8; 32] = [0u8; 32];
193        let mut s_bits: [u8; 32] = [0u8; 32];
194
195        c_bits.copy_from_slice(&bytes[..32]);
196        s_bits.copy_from_slice(&bytes[32..]);
197
198        let c = Option::from(Scalar::from_canonical_bytes(c_bits))
199            .ok_or(TokenError(InternalError::ScalarFormatError))?;
200        let s = Option::from(Scalar::from_canonical_bytes(s_bits))
201            .ok_or(TokenError(InternalError::ScalarFormatError))?;
202
203        Ok(DLEQProof { c, s })
204    }
205}
206
207/// A `BatchDLEQProof` is a proof of the equivalence of the discrete logarithm between a common
208/// pair of points and one or more other pairs of points.
209#[allow(non_snake_case)]
210#[derive(Debug, Clone)]
211pub struct BatchDLEQProof(DLEQProof);
212
213#[cfg(any(test, feature = "base64"))]
214impl_base64!(BatchDLEQProof);
215
216#[cfg(feature = "serde")]
217impl_serde!(BatchDLEQProof);
218
219#[allow(non_snake_case)]
220impl BatchDLEQProof {
221    fn calculate_composites<D>(
222        blinded_tokens: &[BlindedToken],
223        signed_tokens: &[SignedToken],
224        public_key: &PublicKey,
225    ) -> Result<(RistrettoPoint, RistrettoPoint), TokenError>
226    where
227        D: Digest<OutputSize = U64> + Default,
228    {
229        if blinded_tokens.len() != signed_tokens.len() {
230            return Err(TokenError(InternalError::LengthMismatchError));
231        }
232
233        let mut h = D::default();
234
235        h.update(constants::RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
236        h.update(public_key.0.as_bytes());
237
238        for (Pi, Qi) in blinded_tokens.iter().zip(signed_tokens.iter()) {
239            h.update(Pi.0.as_bytes());
240            h.update(Qi.0.as_bytes());
241        }
242
243        let result = h.finalize();
244
245        let mut seed: [u8; 32] = [0u8; 32];
246        seed.copy_from_slice(&result[..32]);
247
248        let mut prng: ChaChaRng = SeedableRng::from_seed(seed);
249        let c_m: Vec<Scalar> = iter::repeat_with(|| Scalar::random(&mut prng))
250            .take(blinded_tokens.len())
251            .collect();
252
253        let M = RistrettoPoint::optional_multiscalar_mul(
254            &c_m,
255            blinded_tokens.iter().map(|Pi| Pi.0.decompress()),
256        )
257        .ok_or(TokenError(InternalError::PointDecompressionError))?;
258
259        let Z = RistrettoPoint::optional_multiscalar_mul(
260            &c_m,
261            signed_tokens.iter().map(|Qi| Qi.0.decompress()),
262        )
263        .ok_or(TokenError(InternalError::PointDecompressionError))?;
264
265        Ok((M, Z))
266    }
267
268    /// Construct a new `BatchDLEQProof`
269    pub fn new<D, T>(
270        rng: &mut T,
271        blinded_tokens: &[BlindedToken],
272        signed_tokens: &[SignedToken],
273        signing_key: &SigningKey,
274    ) -> Result<Self, TokenError>
275    where
276        D: Digest<OutputSize = U64> + Default,
277        T: Rng + CryptoRng,
278    {
279        let (M, Z) = BatchDLEQProof::calculate_composites::<D>(
280            blinded_tokens,
281            signed_tokens,
282            &signing_key.public_key,
283        )?;
284        Ok(BatchDLEQProof(DLEQProof::_new::<D, T>(
285            rng,
286            M,
287            Z,
288            signing_key,
289        )))
290    }
291
292    /// Verify a `BatchDLEQProof`
293    pub fn verify<D>(
294        &self,
295        blinded_tokens: &[BlindedToken],
296        signed_tokens: &[SignedToken],
297        public_key: &PublicKey,
298    ) -> Result<(), TokenError>
299    where
300        D: Digest<OutputSize = U64> + Default,
301    {
302        let (M, Z) =
303            BatchDLEQProof::calculate_composites::<D>(blinded_tokens, signed_tokens, public_key)?;
304
305        self.0._verify::<D>(M, Z, public_key)
306    }
307
308    /// Verify the `BatchDLEQProof` then unblind the `SignedToken`s using each corresponding `Token`
309    pub fn verify_and_unblind<'a, D, I>(
310        &self,
311        tokens: I,
312        blinded_tokens: &[BlindedToken],
313        signed_tokens: &[SignedToken],
314        public_key: &PublicKey,
315    ) -> Result<Vec<UnblindedToken>, TokenError>
316    where
317        D: Digest<OutputSize = U64> + Default,
318        I: IntoIterator<Item = &'a Token>,
319    {
320        self.verify::<D>(blinded_tokens, signed_tokens, public_key)?;
321
322        let unblinded_tokens: Result<Vec<UnblindedToken>, TokenError> = tokens
323            .into_iter()
324            .zip(signed_tokens.iter())
325            .map(|(token, signed_token)| token.unblind(signed_token))
326            .collect();
327        unblinded_tokens.and_then(|unblinded_tokens| {
328            if unblinded_tokens.len() != signed_tokens.len() {
329                return Err(TokenError(InternalError::LengthMismatchError));
330            }
331            Ok(unblinded_tokens)
332        })
333    }
334}
335
336impl BatchDLEQProof {
337    /// Convert this `BatchDLEQProof` to a byte array.
338    pub fn to_bytes(&self) -> [u8; DLEQ_PROOF_LENGTH] {
339        self.0.to_bytes()
340    }
341
342    #[cfg(feature = "serde")]
343    fn bytes_length_error() -> TokenError {
344        DLEQProof::bytes_length_error()
345    }
346
347    /// Construct a `BatchDLEQProof` from a slice of bytes.
348    pub fn from_bytes(bytes: &[u8]) -> Result<BatchDLEQProof, TokenError> {
349        DLEQProof::from_bytes(bytes).map(BatchDLEQProof)
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use base64::{engine::Engine as _, prelude::BASE64_STANDARD};
356    use curve25519_dalek::ristretto::CompressedRistretto;
357    use rand::rngs::OsRng;
358    use sha2::Sha512;
359
360    use super::*;
361    use crate::oprf::Token;
362
363    #[test]
364    #[allow(non_snake_case)]
365    fn works() {
366        let mut rng = OsRng;
367
368        let key1 = SigningKey::random(&mut rng);
369        let key2 = SigningKey::random(&mut rng);
370
371        let P = RistrettoPoint::random(&mut rng);
372        let Q = key1.k * P;
373
374        let proof = DLEQProof::_new::<Sha512, _>(&mut rng, P, Q, &key1);
375
376        assert!(proof._verify::<Sha512>(P, Q, &key1.public_key).is_ok());
377
378        let P = RistrettoPoint::random(&mut rng);
379        let Q = key2.k * P;
380
381        let proof = DLEQProof::_new::<Sha512, _>(&mut rng, P, Q, &key1);
382
383        assert!(proof._verify::<Sha512>(P, Q, &key1.public_key).is_err());
384    }
385
386    #[allow(non_snake_case)]
387    #[test]
388    fn vector_tests() {
389        // Generated using tools/dleq-test-gen
390        let vectors = [
391("SlPD+7xZlw7l+Fr4E4dd/8E6kEouU65+ZfoN6m5iyQE=", "nGajOcg0T5IvwyBstdroFKWUwBd90yNcJU2cQJpluAg=", "COY3iHwUTGQZ9q2bycTgMwgHcLZc+VqICT9kNoeNnTc=", "VLYFn6ZhrZpSHcRS2ffzbtYod1r6gWRc54hVFzXW+1s=", "4hl2AKMk2ueO7f67sHGol531jPu4DfDtDPrkFS10DgVj6RSbG13zWMt+KjpwVtAUdEMkJuYv8b0NPaBZrMlaAg=="),
392("rBKvQjzCywrH+WAHjvVpB4P59cy1A0CCcYjeUioWdA0=", "fEgJRiE5cPsSkdQR+gM+EeGbAja5GYenhQPRuirUGHM=", "Hnvv9L/So4IvS2FPpzvRJL3pZ9ow01IHri3qefZ673Y=", "mszTCP66IXeEPOV7fvVR7bLj3NwxweLxH8wq3XQCTDc=", "u9DkAnPlj21cKR0CHhuGFbi419mRl0fnB5/s6VhDVABwLm1C7zLpiCwRzFHyEwkIBam/2Nt+/ZsuZL9B5cZpDQ=="),
393("vu+HsyYOp5bteH7HKTl22hV/pgqGg1Xo3LIDW56ASQY=", "0EnIefouYrEhIxV06aSvEb0wlADk9pPt2IRJAPkBPAA=", "dpF+MlDg5pNOH0C53DhdWAnsqLz6haKZx/6M+wfzsz4=", "dgvVTGJZTUiGQgRZqcKq26a0ERoNGPNtMJ2Hromjox4=", "7NoWAv8wvYJoNZtPmRK1L+xpbR2NDOpbnh/8ccGiOABfxGf6f/BmQjVDZQiGbYXnyQjzM5NfcjiAP/shOK5lCg=="),
394("tviSLm/W8oFds67y9lMs990fjh08hQNV17/4V2bmOQY=", "5ufRlCvVKvXp1yuxxS7Jvw9LSwQUl6Q/MlT6HY2l1Hc=", "zOVEbK4KQ1GBW97YUVNguoN+NntwtGi1t+EeioMusXY=", "lH2gNbwqSC1nYYxT3I7fNQagTsD4OvSbzwrSCpanQkQ=", "NJF9U3TWiCWMd6Qh/vA90F/2N6udsXbTvifNxf0rzgbhInoEvYDi5jZAZUQEi7x7mmP8iFq7+ukoOroy6/8jCw=="),
395("Ge3prZ2jJSoh1A3ZvrSfaSA1kDziGW2I+Gmh6jniaAs=", "2nNCd5YN9H5EYlOL9/kmLYNBMkaLwnG3wjyd7jw2QAY=", "YHdAzlpSTAMy3mB+F4mPwlyVl+V9Yt4f3cDPNJpWdns=", "gEnqgXg3FDaCQFayTXrIfpbZ2n0P6FD/95LuMsdIfFk=", "Fj2/YunbQs5XxSyLxl/fC4dAfRlErGurTtHHSfGKyQTzrLZrO7VghmGFQaMAXZ+jg+6v99YL6FWj1Y/5WFt2Aw=="),
396        ];
397        for (k, Y, P, Q_b64, dleq_b64) in vectors {
398            let server_key = SigningKey::decode_base64(k).unwrap();
399
400            assert_eq!(server_key.public_key.encode_base64(), Y);
401
402            let P_bytes = BASE64_STANDARD.decode(P).unwrap();
403            let mut P_bits: [u8; 32] = [0u8; 32];
404            P_bits.copy_from_slice(&P_bytes[..32]);
405            let P = CompressedRistretto(P_bits).decompress().unwrap();
406
407            let Q = P * server_key.k;
408
409            assert_eq!(BASE64_STANDARD.encode(&Q.compress().to_bytes()[..]), Q_b64);
410
411            let seed: [u8; 32] = [0u8; 32];
412            let mut prng: ChaChaRng = SeedableRng::from_seed(seed);
413
414            let dleq = DLEQProof::_new::<Sha512, _>(&mut prng, P, Q, &server_key);
415            assert_eq!(dleq.encode_base64(), dleq_b64);
416
417            assert!(dleq._verify::<Sha512>(P, Q, &server_key.public_key).is_ok());
418        }
419    }
420
421    #[allow(non_snake_case)]
422    #[test]
423    fn batch_vector_tests() {
424        // Generated using tools/dleq-test-gen
425        let vectors = [
426("8n06lJH/lkGHsPVPehyAwYA4BX97JbFmDmJzPO1oIQE=", "4KbMr7/9/ZxNG2Baixy/3MFFOCzFFfJLjsUAoetKOjo=", "xGUA5fGnGgvg1zzC3mWeq7K2jqVBfJwJ2uJ6upqPxgA=,LHTaaZi7YqIsuqJ1P+HV8vYIwHFKePF+xton737J3SE=,8MFJE9IYULJT+LsiI3h+kDLc4rmd809utaDo1xic3GQ=,HOMibeqOkyJsN+N5eRJrQkAT+G32L6eOhU+1/Drsbks=,PGLb/Ael7MnFYaS6YjCR/lRIkKONoAsEKuJ5SRvJMwg=", "fgOAdXLxUs/pfT3ustR9mBbUyIbcx1Nis0zWuIz8TS8=,Lh0ekCvZ0cWhDTTxUQV3frSb2+hW9mDWunYWGv/foTA=,EvA36hZe6DD9WOU/F7rnJ8kgXyUgDZ908Li4F1IqAko=,gtbw9qu9qpp1nYEejzzhsWkMyOM8kbvOOMBTdArj82E=,ypADHr5ETijWYAQTw9f6J7OSIk24S1JwBVrgrIsAAE8=", "jhS4wBez3EdGoGEW4ENMW0UpZHn1aiJa9WUWp5RJHyk=", "xJfJMAnGI0ZrEuJcuBaa7cZ84HCe62f9eWIQE2JeiUU=", "X9bPqHlbzd+PzBCTMoZMdF8joNsbZkODlPABwhgD7w3XLP6J8BQP80bfgagB3pXWwa0Uhd1NquBhOL5uCNy9DA=="),
427("iE4eMIMtnV1rgsSMpuE9rfT486QnmuyNAsDB8JSwkgI=", "MIo6R2WZkAylW7XeUOwcYClUOpXj9wZ14Tr6wYY7FDs=", "flw+gZoFAcOlEPmGqr3LliVkshxK5XfBNlow5X+clSs=,roiJVY4XvfsBh6iGSzz9OM/40Nx4VrG9SriyifVOc0A=,UMnZMVFyE9keC0PSx2KSZ2Y8DEM8tT6/XGQAV6AXGBg=,dPcHlc7Q3X/9F9uKd4T2bCk9uOANGBoh72N4tyn/6Xg=,pHS/yXXFw+wYP8FOAqKFCyzuI8552dyKoaEVcA9PXnw=", "sL8mkkfISTi+0cvfZLaB+bb0zbp9vSo3WEt8Q/t5VR0=,quVLKurPfGk/waJnPXniatNV0byGYL9R04PI0Bap5H0=,/naRAvDSk/6QwHC5dFuGKqCwB6Jy+r30EqL2+5y06XI=,Ms7MKju+/YdXOaMjYRouki5VhPA3O6Ng8Hw4DDRdAxU=,6A2sYddm/4xRSJkTpUDrPUuK1Cy0OCUpUthlTgjEQWM=", "an0BjUmpwb/SON3OS/Unoj4+AJ0I7+27WS83LFKtKk4=", "rIv5UFDK/jGS2jy/tHV4vKMXdr8bDMjJt6JFOksb7gQ=", "LR8s0d+woWFwCJpdDek4cWt6d0lbiGoEZMmxa+rILggX0zKz5/T2QQr4qQJAXSWnkySjIwVGtydn+sha8nJJBw=="),
428("CDATdJXtmEMd+a8MiNyBzJfml/tlFeUt+7fqfSdvGgI=", "oCjGp1LXIuALA14oUuDzMvXxZQqNmA9zQvpW7gj8gQc=", "uqzF6Yud/TcOb0Qqh6W5pVXDVTfccpd6vDFKl6cko2s=,1JHJSOeBP4YZJU8vxBQJky++2CCnSXR7AiVYhWIcxhc=,xlt2BWmGn2HHNQRMouv/4CZTjbA5OzAW/OUHy/xMRwc=,jneVhZ2uILeea08oFNHrsMrySZ5NpGZ2rIT8pM2IPxM=,KFtx8ceLvtuL2+dRPm7bDZ5FdrL0f/TRNNkIzMtttl4=", "mF7kNktgoP9OHSA8mgm6t+Vmx9bhO79TG0AjYNjbz24=,7IbBiJX/cH4//616F8UCgo2Lzh3RATwRlrtLl2GmjlM=,YBl7hRaHTwQ19HwtbrVcRTBQ3xhpDrb6EjzS80LO2wQ=,Rs6W8kUZCBam3BZKyvC/Zc7S3bVIWEBXzNVwKsSk/Us=,ylT/xqEBYjWDWfZd0lH9w08/c55SsA9KSgAOtNz48HE=", "AjQpqYvy6onAHHyD8ZtsVGYW/Y84bwvusSy3MEYyQng=", "wESOv7FzkbDc3NJraYWj58aeFRXbt0VAEb1PgLsJ73g=", "hDDZcM6e+X9UV8ZtV9QwQ7w4PeHb+A1ug7WfsKiAXQEHk7P4LIrXSH2AvI09NGUG642xKweGXmkvELD3Q7yxAQ=="),
429("siv+BM3AvP8Jv1aL4MFhMs9Xa6jxUNhFXpTWDfGrZQQ=", "XgFOlHEz5zm5dtx6ptYIXNg1NsJ/3vAq+cf/9eBkbxI=", "dsaMl4/9FcOFtaW3l65y1Z9ETJR36aTcXPMp+w4HGUY=,aH2q1HiReMA/Ney2NNZCgl+5GKK9xrxVwdC+THq9pFY=,uGRqS51VD7DuK0gSpMb3owRld57W6DqOyZpygXJVpmI=,Bvv+lqtCg39SD1H218rPZdQTmYPe2HD3QScntqw1oFA=,9IHWUyv/SCwZ4WKEGi58+bQ5nHsaDBXCku2vOzGvgUY=", "4lPV/OyNjVy4VTUvaROxCuq4ryfegkt7jt5IhrX9THo=,4EAmV5Mv3a/IQFsfVlaFxErNc96Ns980FT4yLlCdoxA=,bgGM37uBMLdRRAd1cu/4Iq+FzFwzRFLVhqp2uGFnPQc=,rCn4OuWnV5tTsgcPJAYRSqfONZf9k/92fwzWHtUxxh8=,lHCDzazDlU0w735u7OQmJM96WGeaNFILanawmC9EwFE=", "jotItTWLW/kpDeh8KJQtNqM7ON0YibEJ7R8VnMHP7Cs=", "Pu49xb3Ixn+Dfg6s+wgjyoPy7ickB5lM7/MxQVdpaUI=", "N6vDmGbYZ0aa9S2JqWSYppiX1AV33QDXGc8FHaF0aQ9y6Hp68UEkI2x5AJQ3URqS+5/x1AuucMH0AOMcLNqODg=="),
430("jTTf/D0gicaG++cQJ1X4qYaOqk4YPo0p6Mo2B95kJAg=", "LCtewONuYTXljy+oK73/m7CON/vr/e1r4aDaVE3xDnc=", "GsomH3aBo6qBHaNGzZZ/pNOviBTbZrUfpthgYU5jAmU=,wHvznjZDA9L8dgQGEj7wf1/QGxunE5/WYdxUpQX4Umc=,ZMxkDV7epUgmpix38jBfWv42VeMQefypY56dnysikWA=,aEy+/J0AFmjYGTjcv5y942fleEk/0rwqlD+kXSn0vCM=,QJMHfIYUraWdJKzenROtgLyjU9MrDtDDwFfDNIcjCHA=", "aGUXY5bBYoDmw4x1muwHzKp1w2sITQMeDfsyxv9EUDc=,qh0wfC/wAdclIJ79R+IIpPJLJM5aBe/i5i54dQOn3Vk=,zCi7XaHyO/b9SfN2AYuJcC60zqnIorkXMjjbixhEKxk=,XlBf7DyH5FtcUyr9Gfnj8i3cnKKPtWGHXm/LpIQX4gM=,wHtZg2i40wdxQvHehZEunTHiODSuEMv8suwFaqynmTQ=", "osUDqpps33Jw0k7vEHFCAk+iywlE7YrXrX5RfqC0olA=", "LnD9wlZrDo3v6dw56owm6NazoBLKwtqMPWdtxSRVWxc=", "KVEotBgIaz5Rymqpy4paroHGQyD/80FdvLCrONxDzQUWgNZxZ6aiCJ2VxIGP+6+86FZXS1sXGgs3dwft/VMCCw=="),
431        ];
432        for (k, Y, P, Q, M_b64, Z_b64, dleq_b64) in vectors {
433            let server_key = SigningKey::decode_base64(k).unwrap();
434
435            assert_eq!(server_key.public_key.encode_base64(), Y);
436
437            let P: Vec<&str> = P.split(',').collect();
438            let Q: Vec<&str> = Q.split(',').collect();
439
440            let P: Vec<BlindedToken> = P
441                .iter()
442                .map(|P_i| BlindedToken::decode_base64(P_i).unwrap())
443                .collect();
444
445            let Q: Vec<SignedToken> = P
446                .iter()
447                .zip(Q.into_iter())
448                .map(|(P_i, Q_i_b64)| {
449                    let Q_i = server_key.sign(P_i).unwrap();
450                    assert_eq!(Q_i.encode_base64(), Q_i_b64);
451                    Q_i
452                })
453                .collect();
454
455            let (M, Z) =
456                BatchDLEQProof::calculate_composites::<Sha512>(&P, &Q, &server_key.public_key)
457                    .unwrap();
458
459            assert_eq!(BASE64_STANDARD.encode(&M.compress().to_bytes()[..]), M_b64);
460            assert_eq!(BASE64_STANDARD.encode(&Z.compress().to_bytes()[..]), Z_b64);
461
462            let seed: [u8; 32] = [0u8; 32];
463            let mut prng: ChaChaRng = SeedableRng::from_seed(seed);
464
465            let batch_proof =
466                BatchDLEQProof::new::<Sha512, _>(&mut prng, &P, &Q, &server_key).unwrap();
467            assert_eq!(batch_proof.encode_base64(), dleq_b64);
468
469            assert!(batch_proof
470                .verify::<Sha512>(&P, &Q, &server_key.public_key)
471                .is_ok());
472        }
473    }
474
475    #[test]
476    #[allow(non_snake_case)]
477    fn batch_works() {
478        use std::vec::Vec;
479
480        let mut rng = OsRng;
481
482        let key = SigningKey::random(&mut rng);
483
484        let blinded_tokens = vec![Token::random::<Sha512, _>(&mut rng).blind()];
485        let signed_tokens: Vec<SignedToken> = blinded_tokens
486            .iter()
487            .filter_map(|t| key.sign(t).ok())
488            .collect();
489
490        let batch_proof =
491            BatchDLEQProof::new::<Sha512, _>(&mut rng, &blinded_tokens, &signed_tokens, &key)
492                .unwrap();
493
494        assert!(batch_proof
495            .verify::<Sha512>(&blinded_tokens, &signed_tokens, &key.public_key)
496            .is_ok());
497    }
498}