1#![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
51pub trait AttrScalars {
58 type Storage: ArrayLike<Scalar> + Copy + Eq + Serialize + for<'a> Deserialize<'a>;
60
61 const NUM_ATTRS: usize = Self::Storage::LEN;
65}
66
67impl AttrScalars for AuthCredential {
68 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 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 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 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 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#[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#[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#[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#[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 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(¶ms));
434 assert!(SystemParams::generate() == SystemParams::get_hardcoded());
435 }
436
437 #[test]
438 fn test_mac() {
439 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}