zkgroup/api/
zk_credential_key.rs1#![allow(non_snake_case)]
16
17use curve25519_dalek_signal::ristretto::RistrettoPoint;
18use curve25519_dalek_signal::scalar::Scalar;
19use partial_default::PartialDefault;
20use poksho::ShoApi;
21use serde::{Deserialize, Serialize};
22use zkcredential::sho::ShoExt as _;
23
24use crate::RandomnessBytes;
25use crate::common::serialization::ReservedByte;
26
27#[derive(Clone, Deserialize, PartialDefault)]
28#[serde(from = "ZkCredentialPrivateKey")]
29pub struct ZkCredentialKeyPair {
30 secret: Scalar,
31 public: RistrettoPoint,
32}
33
34#[derive(Clone, Serialize, Deserialize, PartialDefault)]
39struct ZkCredentialPrivateKey {
40 reserved: ReservedByte,
41 secret: Scalar,
42}
43
44impl ZkCredentialKeyPair {
45 pub fn generate(randomness: RandomnessBytes) -> Self {
46 let mut sho = poksho::ShoHmacSha256::new(b"20260520_Signal_ZkCredentialKeyPair_Generate");
47 sho.absorb_and_ratchet(&randomness);
48 let secret = sho.get_scalar();
49 let public = RistrettoPoint::mul_base(&secret);
50 Self { secret, public }
51 }
52
53 pub fn public_key(&self) -> ZkCredentialPublicKey {
54 ZkCredentialPublicKey {
55 reserved: Default::default(),
56 public: self.public,
57 }
58 }
59
60 pub(crate) fn secret(&self) -> Scalar {
61 self.secret
62 }
63}
64
65impl From<ZkCredentialPrivateKey> for ZkCredentialKeyPair {
66 fn from(value: ZkCredentialPrivateKey) -> Self {
67 let ZkCredentialPrivateKey {
68 reserved: _,
69 secret,
70 } = value;
71 let public = RistrettoPoint::mul_base(&secret);
72 Self { secret, public }
73 }
74}
75
76impl Serialize for ZkCredentialKeyPair {
77 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78 where
79 S: serde::Serializer,
80 {
81 ZkCredentialPrivateKey {
82 reserved: Default::default(),
83 secret: self.secret,
84 }
85 .serialize(serializer)
86 }
87}
88
89#[derive(Clone, Copy, Serialize, Deserialize, PartialDefault)]
93pub struct ZkCredentialPublicKey {
94 reserved: ReservedByte,
95 public: RistrettoPoint,
96}
97
98impl ZkCredentialPublicKey {
99 pub(crate) fn point(&self) -> RistrettoPoint {
100 self.public
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::RANDOMNESS_LEN;
108
109 #[test]
110 fn generate_is_deterministic() {
111 let r: RandomnessBytes = [0x7Au8; RANDOMNESS_LEN];
112 let a = ZkCredentialKeyPair::generate(r);
113 let b = ZkCredentialKeyPair::generate(r);
114 assert_eq!(a.secret, b.secret);
115 assert_eq!(a.public, b.public);
116 }
117
118 #[test]
119 fn roundtrip_keypair() {
120 let r: RandomnessBytes = [0x11u8; RANDOMNESS_LEN];
121 let kp = ZkCredentialKeyPair::generate(r);
122 let bytes = crate::serialize(&kp);
123 let parsed: ZkCredentialKeyPair = crate::deserialize(&bytes).expect("roundtrip");
124 assert_eq!(kp.secret, parsed.secret);
125 assert_eq!(kp.public, parsed.public);
126 }
127
128 #[test]
129 fn roundtrip_public_key() {
130 let r: RandomnessBytes = [0x22u8; RANDOMNESS_LEN];
131 let pk = ZkCredentialKeyPair::generate(r).public_key();
132 let bytes = crate::serialize(&pk);
133 let parsed: ZkCredentialPublicKey = crate::deserialize(&bytes).expect("roundtrip");
134 assert_eq!(pk.public, parsed.public);
135 }
136
137 #[test]
138 fn public_key_derives_from_secret() {
139 let r: RandomnessBytes = [0x33u8; RANDOMNESS_LEN];
140 let kp = ZkCredentialKeyPair::generate(r);
141 let pk = kp.public_key();
142 assert_eq!(RistrettoPoint::mul_base(&kp.secret), pk.public);
143 }
144}