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 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
49pub trait AttrScalars {
56 type Storage: ArrayLike<Scalar> + Copy + Eq + Serialize + for<'a> Deserialize<'a>;
58
59 const NUM_ATTRS: usize = Self::Storage::LEN;
63}
64
65impl AttrScalars for AuthCredential {
66 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 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 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 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 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 *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#[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#[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#[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#[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 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(¶ms));
454 assert!(SystemParams::generate() == SystemParams::get_hardcoded());
455 }
456
457 #[test]
458 fn test_mac() {
459 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}