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