zkgroup/crypto/
credentials.rs

1//
2// Copyright 2020-2022 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6#![allow(non_snake_case)]
7
8use std::sync::LazyLock;
9
10use const_str::hex;
11use curve25519_dalek_signal::constants::RISTRETTO_BASEPOINT_POINT;
12use curve25519_dalek_signal::ristretto::RistrettoPoint;
13use curve25519_dalek_signal::scalar::Scalar;
14use partial_default::PartialDefault;
15use serde::{Deserialize, Serialize};
16
17use crate::common::array_utils::{ArrayLike, OneBased};
18use crate::common::sho::*;
19use crate::common::simple_types::*;
20use crate::crypto::receipt_struct::ReceiptStruct;
21use crate::crypto::timestamp_struct::TimestampStruct;
22use crate::crypto::{
23    profile_key_credential_request, receipt_credential_request, receipt_struct, uid_struct,
24};
25use crate::{
26    NUM_AUTH_CRED_ATTRIBUTES, NUM_PROFILE_KEY_CRED_ATTRIBUTES, NUM_RECEIPT_CRED_ATTRIBUTES,
27};
28
29static SYSTEM_PARAMS: LazyLock<SystemParams> =
30    LazyLock::new(|| crate::deserialize::<SystemParams>(SystemParams::SYSTEM_HARDCODED).unwrap());
31
32const NUM_SUPPORTED_ATTRS: usize = 6;
33#[derive(Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub struct SystemParams {
35    pub(crate) G_w: RistrettoPoint,
36    pub(crate) G_wprime: RistrettoPoint,
37    pub(crate) G_x0: RistrettoPoint,
38    pub(crate) G_x1: RistrettoPoint,
39    pub(crate) G_y: OneBased<[RistrettoPoint; NUM_SUPPORTED_ATTRS]>,
40    pub(crate) G_m1: RistrettoPoint,
41    pub(crate) G_m2: RistrettoPoint,
42    pub(crate) G_m3: RistrettoPoint,
43    pub(crate) G_m4: RistrettoPoint,
44    pub(crate) G_m5: RistrettoPoint,
45    pub(crate) G_V: RistrettoPoint,
46    pub(crate) G_z: RistrettoPoint,
47}
48
49/// Used to specialize a [`KeyPair<S>`] to support a certain number of attributes.
50///
51/// The only required member is `Storage`, which should be a fixed-size array of [`Scalar`], one for
52/// each attribute. However, for backwards compatibility some systems support fewer attributes than
53/// are actually stored, and in this case the `NUM_ATTRS` member can be set to a custom value. Note
54/// that `NUM_ATTRS` must always be less than or equal to the number of elements in `Storage`.
55pub trait AttrScalars {
56    /// The storage (should be a fixed-size array of Scalar).
57    type Storage: ArrayLike<Scalar> + Copy + Eq + Serialize + for<'a> Deserialize<'a>;
58
59    /// The number of attributes supported in this system.
60    ///
61    /// Defaults to the full set stored in `Self::Storage`.
62    const NUM_ATTRS: usize = Self::Storage::LEN;
63}
64
65impl AttrScalars for AuthCredential {
66    // Store four scalars for backwards compatibility.
67    type Storage = [Scalar; 4];
68    const NUM_ATTRS: usize = NUM_AUTH_CRED_ATTRIBUTES;
69}
70impl AttrScalars for AuthCredentialWithPni {
71    type Storage = [Scalar; 5];
72}
73impl AttrScalars for ProfileKeyCredential {
74    // Store four scalars for backwards compatibility.
75    type Storage = [Scalar; 4];
76    const NUM_ATTRS: usize = NUM_PROFILE_KEY_CRED_ATTRIBUTES;
77}
78impl AttrScalars for ExpiringProfileKeyCredential {
79    type Storage = [Scalar; 5];
80}
81impl AttrScalars for ReceiptCredential {
82    // Store four scalars for backwards compatibility.
83    type Storage = [Scalar; 4];
84    const NUM_ATTRS: usize = NUM_RECEIPT_CRED_ATTRIBUTES;
85}
86impl AttrScalars for PniCredential {
87    type Storage = [Scalar; 6];
88}
89
90#[derive(Serialize, Deserialize, PartialDefault)]
91#[partial_default(bound = "S::Storage: Default")]
92pub struct KeyPair<S: AttrScalars> {
93    // private
94    pub(crate) w: Scalar,
95    pub(crate) wprime: Scalar,
96    pub(crate) W: RistrettoPoint,
97    pub(crate) x0: Scalar,
98    pub(crate) x1: Scalar,
99    pub(crate) y: OneBased<S::Storage>,
100
101    // public
102    pub(crate) C_W: RistrettoPoint,
103    pub(crate) I: RistrettoPoint,
104}
105
106impl<S: AttrScalars> Clone for KeyPair<S> {
107    fn clone(&self) -> Self {
108        // Rely on Copy
109        *self
110    }
111}
112
113impl<S: AttrScalars> Copy for KeyPair<S> {}
114
115impl<S: AttrScalars> PartialEq for KeyPair<S> {
116    fn eq(&self, other: &Self) -> bool {
117        self.w == other.w
118            && self.wprime == other.wprime
119            && self.W == other.W
120            && self.x0 == other.x0
121            && self.x1 == other.x1
122            && self.y == other.y
123            && self.C_W == other.C_W
124            && self.I == other.I
125    }
126}
127impl<S: AttrScalars> Eq for KeyPair<S> {}
128
129#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
130pub struct PublicKey {
131    pub(crate) C_W: RistrettoPoint,
132    pub(crate) I: RistrettoPoint,
133}
134
135/// Unused, kept only because ServerSecretParams contains a `KeyPair<AuthCredential>`.
136#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
137pub(crate) struct AuthCredential {
138    pub(crate) t: Scalar,
139    pub(crate) U: RistrettoPoint,
140    pub(crate) V: RistrettoPoint,
141}
142
143/// Unused, kept only because ServerSecretParams contains a `KeyPair<AuthCredentialWithPni>`.
144#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
145pub(crate) struct AuthCredentialWithPni {
146    pub(crate) t: Scalar,
147    pub(crate) U: RistrettoPoint,
148    pub(crate) V: RistrettoPoint,
149}
150
151/// Unused, kept only because ServerSecretParams contains a `KeyPair<ProfileKeyCredential>`.
152#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub struct ProfileKeyCredential {
154    pub(crate) t: Scalar,
155    pub(crate) U: RistrettoPoint,
156    pub(crate) V: RistrettoPoint,
157}
158
159#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
160pub struct ExpiringProfileKeyCredential {
161    pub(crate) t: Scalar,
162    pub(crate) U: RistrettoPoint,
163    pub(crate) V: RistrettoPoint,
164}
165
166#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
167pub struct BlindedExpiringProfileKeyCredentialWithSecretNonce {
168    pub(crate) rprime: Scalar,
169    pub(crate) t: Scalar,
170    pub(crate) U: RistrettoPoint,
171    pub(crate) S1: RistrettoPoint,
172    pub(crate) S2: RistrettoPoint,
173}
174
175#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
176pub struct BlindedExpiringProfileKeyCredential {
177    pub(crate) t: Scalar,
178    pub(crate) U: RistrettoPoint,
179    pub(crate) S1: RistrettoPoint,
180    pub(crate) S2: RistrettoPoint,
181}
182
183/// Unused, kept only because ServerSecretParams contains a `KeyPair<PniCredential>`.
184#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub struct PniCredential {
186    pub(crate) t: Scalar,
187    pub(crate) U: RistrettoPoint,
188    pub(crate) V: RistrettoPoint,
189}
190
191#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
192pub struct ReceiptCredential {
193    pub(crate) t: Scalar,
194    pub(crate) U: RistrettoPoint,
195    pub(crate) V: RistrettoPoint,
196}
197
198#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
199pub struct BlindedReceiptCredentialWithSecretNonce {
200    pub(crate) rprime: Scalar,
201    pub(crate) t: Scalar,
202    pub(crate) U: RistrettoPoint,
203    pub(crate) S1: RistrettoPoint,
204    pub(crate) S2: RistrettoPoint,
205}
206
207#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialDefault)]
208pub struct BlindedReceiptCredential {
209    pub(crate) t: Scalar,
210    pub(crate) U: RistrettoPoint,
211    pub(crate) S1: RistrettoPoint,
212    pub(crate) S2: RistrettoPoint,
213}
214
215pub(crate) fn convert_to_points_receipt_struct(
216    receipt: receipt_struct::ReceiptStruct,
217) -> Vec<RistrettoPoint> {
218    let system = SystemParams::get_hardcoded();
219    let m1 = receipt.calc_m1();
220    let receipt_serial_scalar = encode_receipt_serial_bytes(receipt.receipt_serial_bytes);
221    vec![m1 * system.G_m1, receipt_serial_scalar * system.G_m2]
222}
223
224pub(crate) fn convert_to_point_M2_receipt_serial_bytes(
225    receipt_serial_bytes: ReceiptSerialBytes,
226) -> RistrettoPoint {
227    let system = SystemParams::get_hardcoded();
228    let receipt_serial_scalar = encode_receipt_serial_bytes(receipt_serial_bytes);
229    receipt_serial_scalar * system.G_m2
230}
231
232impl SystemParams {
233    #[cfg(test)]
234    fn generate() -> Self {
235        let mut sho = Sho::new(
236            b"Signal_ZKGroup_20200424_Constant_Credentials_SystemParams_Generate",
237            b"",
238        );
239        let G_w = sho.get_point();
240        let G_wprime = sho.get_point();
241
242        let G_x0 = sho.get_point();
243        let G_x1 = sho.get_point();
244
245        let G_y1 = sho.get_point();
246        let G_y2 = sho.get_point();
247        let G_y3 = sho.get_point();
248        let G_y4 = sho.get_point();
249
250        let G_m1 = sho.get_point();
251        let G_m2 = sho.get_point();
252        let G_m3 = sho.get_point();
253        let G_m4 = sho.get_point();
254
255        let G_V = sho.get_point();
256        let G_z = sho.get_point();
257
258        // We don't ever want to use existing generator points in new ways,
259        // so new points have to be added at the end.
260        let G_y5 = sho.get_point();
261        let G_y6 = sho.get_point();
262
263        let G_m5 = sho.get_point();
264
265        SystemParams {
266            G_w,
267            G_wprime,
268            G_x0,
269            G_x1,
270            G_y: OneBased([G_y1, G_y2, G_y3, G_y4, G_y5, G_y6]),
271            G_m1,
272            G_m2,
273            G_m3,
274            G_m4,
275            G_m5,
276            G_V,
277            G_z,
278        }
279    }
280
281    pub fn get_hardcoded() -> SystemParams {
282        *SYSTEM_PARAMS
283    }
284
285    const SYSTEM_HARDCODED: &'static [u8] = &hex!(
286        "9ae7c8e5ed779b114ae7708aa2f794670adda324987b659913122c35505b105e6ca31025d2d76be7fd34944f98f7fa0e37babb2c8b98bbbdbd3dd1bf130cca2c8a9a3bdfaaa2b6b322d46b93eca7b0d51c86a3c839e11466358258a6c10c577fc2bffd34cd99164c9a6cd29fab55d91ff9269322ec3458603cc96a0d47f704058288f62ee0acedb8aa23242121d98965a9bb2991250c11758095ece0fd2b33285286fe1fcb056103b6081744b975f550d08521568dd3d8618f25c140375a0f4024c3aa23bdfffb27fbd982208d3ecd1fd3bcb7ac0c3a14b109804fc748d7fa456cffb4934f980b6e09a248a60f44a6150ae6c13d7e3c06261d7e4eed37f39f60b04dd9d607fd357012274d3c63dbb38e7378599c9e97dfbb28842694891d5f0ddc729919b798b4131503408cc57a9c532f4427632c88f54cea53861a5bc44c61cc6037dc31c2e8d4474fb519587a448693182ad9d6d86b535957858f547b9340127da75f8074caee944ac36c0ac662d38c9b3ccce03a093fcd9644047398b86b6e83372ff14fb8bb0dea65531252ac70d58a4a0810d682a0e709c9227b30ef6c8e17c5915d527221bb00da8175cd6489aa8aa492a500f9abee5690b9dfca8855dc0bd02a7f277add240f639ac16801e81574afb4683edff63b9a01e93dbd867a04b616c706c80c756c11a3016bbfb60977f4648b5f2395a4b428b7211940813e3afde2b87aa9c2c37bf716e2578f95656df12c2fb6f5d0631f6f71e2c3193f6d"
287    );
288}
289
290impl<S: AttrScalars> KeyPair<S> {
291    pub fn generate(sho: &mut Sho) -> Self {
292        assert!(S::NUM_ATTRS >= 1, "at least one attribute required");
293        assert!(
294            S::NUM_ATTRS <= NUM_SUPPORTED_ATTRS,
295            "more than {NUM_SUPPORTED_ATTRS} attributes not supported"
296        );
297        assert!(
298            S::NUM_ATTRS <= S::Storage::LEN,
299            "more attributes than storage",
300        );
301
302        let system = SystemParams::get_hardcoded();
303        let w = sho.get_scalar();
304        let W = w * system.G_w;
305        let wprime = sho.get_scalar();
306        let x0 = sho.get_scalar();
307        let x1 = sho.get_scalar();
308
309        let y = OneBased::<S::Storage>::create(|| sho.get_scalar());
310
311        let C_W = (w * system.G_w) + (wprime * system.G_wprime);
312        let mut I = system.G_V - (x0 * system.G_x0) - (x1 * system.G_x1);
313
314        for (yn, G_yn) in y.iter().zip(system.G_y.iter()).take(S::NUM_ATTRS) {
315            I -= yn * G_yn;
316        }
317
318        KeyPair {
319            w,
320            wprime,
321            W,
322            x0,
323            x1,
324            y,
325            C_W,
326            I,
327        }
328    }
329
330    pub fn get_public_key(&self) -> PublicKey {
331        PublicKey {
332            C_W: self.C_W,
333            I: self.I,
334        }
335    }
336
337    fn credential_core(
338        &self,
339        M: &[RistrettoPoint],
340        sho: &mut Sho,
341    ) -> (Scalar, RistrettoPoint, RistrettoPoint) {
342        assert!(
343            M.len() <= S::NUM_ATTRS,
344            "more than {} attributes not supported",
345            S::NUM_ATTRS
346        );
347        let t = sho.get_scalar();
348        let U = sho.get_point();
349
350        let mut V = self.W + (self.x0 + self.x1 * t) * U;
351        for (yn, Mn) in self.y.iter().zip(M) {
352            V += yn * Mn;
353        }
354        (t, U, V)
355    }
356}
357
358impl KeyPair<ExpiringProfileKeyCredential> {
359    pub fn create_blinded_expiring_profile_key_credential(
360        &self,
361        uid: uid_struct::UidStruct,
362        public_key: profile_key_credential_request::PublicKey,
363        ciphertext: profile_key_credential_request::Ciphertext,
364        credential_expiration_time: Timestamp,
365        sho: &mut Sho,
366    ) -> BlindedExpiringProfileKeyCredentialWithSecretNonce {
367        let M = [uid.M1, uid.M2];
368
369        let (t, U, Vprime) = self.credential_core(&M, sho);
370
371        let params = SystemParams::get_hardcoded();
372        let m5 = TimestampStruct::calc_m_from(credential_expiration_time);
373        let M5 = m5 * params.G_m5;
374        let Vprime_with_expiration = Vprime + (self.y[5] * M5);
375
376        let rprime = sho.get_scalar();
377        let R1 = rprime * RISTRETTO_BASEPOINT_POINT;
378        let R2 = rprime * public_key.Y + Vprime_with_expiration;
379        let S1 = R1 + (self.y[3] * ciphertext.D1) + (self.y[4] * ciphertext.E1);
380        let S2 = R2 + (self.y[3] * ciphertext.D2) + (self.y[4] * ciphertext.E2);
381        BlindedExpiringProfileKeyCredentialWithSecretNonce {
382            rprime,
383            t,
384            U,
385            S1,
386            S2,
387        }
388    }
389}
390
391impl KeyPair<ReceiptCredential> {
392    pub fn create_blinded_receipt_credential(
393        &self,
394        public_key: receipt_credential_request::PublicKey,
395        ciphertext: receipt_credential_request::Ciphertext,
396        receipt_expiration_time: Timestamp,
397        receipt_level: ReceiptLevel,
398        sho: &mut Sho,
399    ) -> BlindedReceiptCredentialWithSecretNonce {
400        let params = SystemParams::get_hardcoded();
401        let m1 = ReceiptStruct::calc_m1_from(receipt_expiration_time, receipt_level);
402        let M = [m1 * params.G_m1];
403
404        let (t, U, Vprime) = self.credential_core(&M, sho);
405        let rprime = sho.get_scalar();
406        let R1 = rprime * RISTRETTO_BASEPOINT_POINT;
407        let R2 = rprime * public_key.Y + Vprime;
408        let S1 = self.y[2] * ciphertext.D1 + R1;
409        let S2 = self.y[2] * ciphertext.D2 + R2;
410        BlindedReceiptCredentialWithSecretNonce {
411            rprime,
412            t,
413            U,
414            S1,
415            S2,
416        }
417    }
418}
419
420impl BlindedExpiringProfileKeyCredentialWithSecretNonce {
421    pub fn get_blinded_expiring_profile_key_credential(
422        &self,
423    ) -> BlindedExpiringProfileKeyCredential {
424        BlindedExpiringProfileKeyCredential {
425            t: self.t,
426            U: self.U,
427            S1: self.S1,
428            S2: self.S2,
429        }
430    }
431}
432
433impl BlindedReceiptCredentialWithSecretNonce {
434    pub fn get_blinded_receipt_credential(&self) -> BlindedReceiptCredential {
435        BlindedReceiptCredential {
436            t: self.t,
437            U: self.U,
438            S1: self.S1,
439            S2: self.S2,
440        }
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447    use crate::common::constants::*;
448    use crate::crypto::proofs;
449
450    #[test]
451    fn test_system() {
452        let params = SystemParams::generate();
453        println!("PARAMS = {:#x?}", bincode::serialize(&params));
454        assert!(SystemParams::generate() == SystemParams::get_hardcoded());
455    }
456
457    #[test]
458    fn test_mac() {
459        // It doesn't really matter *which* credential we test here, we just want to generally know
460        // we've set things up correctly. (Also, the credentials hardcoded here in zkgroup may
461        // eventually all be superseded by implementations using zkcredential, at which point this
462        // test can be deleted.)
463        let mut sho = Sho::new(b"Test_Credentials", b"");
464        let keypair = KeyPair::<ExpiringProfileKeyCredential>::generate(&mut sho);
465
466        let uid_bytes = TEST_ARRAY_16;
467        let redemption_time = Timestamp::from_epoch_seconds(37 * SECONDS_PER_DAY);
468        let aci = libsignal_core::Aci::from_uuid_bytes(uid_bytes);
469        let aci_struct = uid_struct::UidStruct::from_service_id(aci.into());
470        let profile_key_struct = crate::crypto::profile_key_struct::ProfileKeyStruct::new(
471            [1; PROFILE_KEY_LEN],
472            uid_bytes,
473        );
474        let request_key_pair =
475            crate::crypto::profile_key_credential_request::KeyPair::generate(&mut sho);
476        let ciphertext = request_key_pair
477            .encrypt(profile_key_struct, &mut sho)
478            .get_ciphertext();
479        let credential = keypair.create_blinded_expiring_profile_key_credential(
480            aci_struct,
481            request_key_pair.get_public_key(),
482            ciphertext,
483            redemption_time,
484            &mut sho,
485        );
486        let proof = proofs::ExpiringProfileKeyCredentialIssuanceProof::new(
487            keypair,
488            request_key_pair.get_public_key(),
489            ciphertext,
490            credential,
491            aci_struct,
492            redemption_time,
493            &mut sho,
494        );
495
496        let public_key = keypair.get_public_key();
497        proof
498            .verify(
499                public_key,
500                request_key_pair.get_public_key(),
501                uid_bytes,
502                ciphertext,
503                credential.get_blinded_expiring_profile_key_credential(),
504                redemption_time,
505            )
506            .unwrap();
507
508        let keypair_bytes = bincode::serialize(&keypair).unwrap();
509        let keypair2 = bincode::deserialize(&keypair_bytes).unwrap();
510        assert!(keypair == keypair2);
511
512        let public_key_bytes = bincode::serialize(&public_key).unwrap();
513        let public_key2 = bincode::deserialize(&public_key_bytes).unwrap();
514        assert!(public_key == public_key2);
515
516        let mac_bytes = bincode::serialize(&credential).unwrap();
517
518        println!("mac_bytes = {}", hex::encode(&mac_bytes));
519        assert_eq!(
520            mac_bytes,
521            hex!(
522                "ef47110715831160100f14d1936f4349c45b80ccaacd4edd9f949375d2d90a090888d81f8b0ed313
523                808b5ff7ec1957ed4e8b3d9c195b3a5abdbdd3d972c29809100a7f8dc2354be7a1d44452cbadd87e
524                4851ae05ebeb2586b856d35af765883a94473ad855df8583be2930e4e1d5756175a9091f2be1d8d0
525                21280446a7611841d6b4f2eb165267a9d1d7a800f19c2077a4ef7df721b160fe200181be3c455f1c"
526            )
527        );
528    }
529}