zkgroup/api/
zk_credential_key.rs1#![allow(non_snake_case)]
23
24use std::sync::OnceLock;
25
26use curve25519_dalek::ristretto::RistrettoPoint;
27use curve25519_dalek::scalar::Scalar;
28use partial_default::PartialDefault;
29use poksho::ShoApi;
30use serde::{Deserialize, Serialize};
31use zkcredential::attributes::{Domain, KeyPair, PublicKey, derive_default_generator_points};
32
33use crate::RandomnessBytes;
34use crate::common::serialization::ReservedByte;
35
36pub struct ZkCredentialKeyDomain;
43
44impl Domain for ZkCredentialKeyDomain {
45 type Attribute = [RistrettoPoint; 2];
46
47 const ID: &'static str = "Signal_ZkCredentialKey_20260602";
48
49 fn G_a() -> [RistrettoPoint; 2] {
50 static STORAGE: OnceLock<[RistrettoPoint; 2]> = OnceLock::new();
51 *derive_default_generator_points::<Self>(&STORAGE)
52 }
53}
54
55#[derive(Clone, Serialize, Deserialize, PartialDefault)]
56pub struct ZkCredentialKeyPair {
57 reserved: ReservedByte,
58 inner: KeyPair<ZkCredentialKeyDomain>,
59}
60
61impl ZkCredentialKeyPair {
62 pub fn generate(randomness: RandomnessBytes) -> Self {
63 let mut sho = poksho::ShoHmacSha256::new(b"20260520_Signal_ZkCredentialKeyPair_Generate");
64 sho.absorb_and_ratchet(&randomness);
65 Self {
66 reserved: Default::default(),
67 inner: KeyPair::derive_from(&mut sho),
68 }
69 }
70
71 pub fn public_key(&self) -> ZkCredentialPublicKey {
72 ZkCredentialPublicKey {
73 reserved: Default::default(),
74 inner: self.inner.public_key,
75 }
76 }
77
78 pub(crate) fn secrets(&self) -> (Scalar, Scalar) {
80 (self.inner.a1, self.inner.a2)
81 }
82}
83
84#[derive(Clone, Copy, Serialize, Deserialize, PartialDefault)]
88pub struct ZkCredentialPublicKey {
89 reserved: ReservedByte,
90 inner: PublicKey<ZkCredentialKeyDomain>,
91}
92
93impl ZkCredentialPublicKey {
94 pub(crate) fn point(&self) -> RistrettoPoint {
95 self.inner.A
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::RANDOMNESS_LEN;
103
104 #[test]
105 fn generate_is_deterministic() {
106 let r: RandomnessBytes = [0x7Au8; RANDOMNESS_LEN];
107 let a = ZkCredentialKeyPair::generate(r);
108 let b = ZkCredentialKeyPair::generate(r);
109 assert_eq!(a.secrets(), b.secrets());
110 assert_eq!(a.public_key().point(), b.public_key().point());
111 }
112
113 #[test]
114 fn roundtrip_keypair() {
115 let r: RandomnessBytes = [0x11u8; RANDOMNESS_LEN];
116 let kp = ZkCredentialKeyPair::generate(r);
117 let bytes = crate::serialize(&kp);
118 let parsed: ZkCredentialKeyPair = crate::deserialize(&bytes).expect("roundtrip");
119 assert_eq!(kp.secrets(), parsed.secrets());
120 assert_eq!(kp.public_key().point(), parsed.public_key().point());
121 }
122
123 #[test]
124 fn roundtrip_public_key() {
125 let r: RandomnessBytes = [0x22u8; RANDOMNESS_LEN];
126 let pk = ZkCredentialKeyPair::generate(r).public_key();
127 let bytes = crate::serialize(&pk);
128 let parsed: ZkCredentialPublicKey = crate::deserialize(&bytes).expect("roundtrip");
129 assert_eq!(pk.point(), parsed.point());
130 }
131
132 #[test]
133 fn public_key_derives_from_secrets() {
134 let r: RandomnessBytes = [0x33u8; RANDOMNESS_LEN];
135 let kp = ZkCredentialKeyPair::generate(r);
136 let (a1, a2) = kp.secrets();
137 let [G_a1, G_a2] = ZkCredentialKeyDomain::G_a();
138 assert_eq!(a1 * G_a1 + a2 * G_a2, kp.public_key().point());
139 }
140}