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