zkgroup/api/auth/auth_credential_with_pni/
zkc.rs1use libsignal_core::{Aci, Pni};
7use partial_default::PartialDefault;
8use serde::{Deserialize, Serialize};
9use zkcredential::credentials::{CredentialKeyPair, CredentialPublicKey};
10
11use crate::api::auth::auth_credential_with_pni::AuthCredentialWithPniVersion;
12use crate::common::constants::PRESENTATION_VERSION_4;
13use crate::common::serialization::VersionByte;
14use crate::common::simple_types::{RandomnessBytes, Timestamp};
15use crate::crypto::uid_encryption;
16use crate::crypto::uid_struct::UidStruct;
17use crate::groups::{GroupPublicParams, GroupSecretParams, UuidCiphertext};
18use crate::{ServerPublicParams, ServerSecretParams, ZkGroupVerificationFailure};
19
20const CREDENTIAL_LABEL: &[u8] = b"20240222_Signal_AuthCredentialZkc";
21
22#[derive(Clone, Serialize, Deserialize, PartialDefault)]
27pub struct AuthCredentialWithPniZkc {
28 version: VersionByte<{ AuthCredentialWithPniVersion::Zkc as u8 }>,
29 credential: zkcredential::credentials::Credential,
30 aci: UidStruct,
31 pni: UidStruct,
32 redemption_time: Timestamp,
33}
34
35#[derive(Clone, Serialize, Deserialize, PartialDefault)]
36pub struct AuthCredentialWithPniZkcResponse {
37 version: VersionByte<{ AuthCredentialWithPniVersion::Zkc as u8 }>,
38 proof: zkcredential::issuance::IssuanceProof,
39}
40
41#[derive(Serialize, Deserialize, PartialDefault)]
42pub struct AuthCredentialWithPniZkcPresentation {
43 version: VersionByte<PRESENTATION_VERSION_4>,
44 proof: zkcredential::presentation::PresentationProof,
45 aci_ciphertext: uid_encryption::Ciphertext,
46 pni_ciphertext: uid_encryption::Ciphertext,
47 redemption_time: Timestamp,
48}
49
50impl AuthCredentialWithPniZkcResponse {
51 pub fn issue_credential(
52 aci: Aci,
53 pni: Pni,
54 redemption_time: Timestamp,
55 params: &ServerSecretParams,
56 randomness: RandomnessBytes,
57 ) -> Self {
58 Self::issue_credential_for_key(
59 aci,
60 pni,
61 redemption_time,
62 ¶ms.generic_credential_key_pair,
63 randomness,
64 )
65 }
66
67 pub fn receive(
68 self,
69 aci: Aci,
70 pni: Pni,
71 redemption_time: Timestamp,
72 public_params: &ServerPublicParams,
73 ) -> Result<AuthCredentialWithPniZkc, ZkGroupVerificationFailure> {
74 self.receive_for_key(
75 aci,
76 pni,
77 redemption_time,
78 &public_params.generic_credential_public_key,
79 )
80 }
81
82 pub(crate) fn issue_credential_for_key(
83 aci: Aci,
84 pni: Pni,
85 redemption_time: Timestamp,
86 credential_key: &CredentialKeyPair,
87 randomness: RandomnessBytes,
88 ) -> Self {
89 let proof = zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
90 .add_attribute(&UidStruct::from_service_id(aci.into()))
91 .add_attribute(&UidStruct::from_service_id(pni.into()))
92 .add_public_attribute(&redemption_time)
93 .issue(credential_key, randomness);
94
95 Self {
96 version: VersionByte,
97 proof,
98 }
99 }
100
101 pub(crate) fn receive_for_key(
102 self,
103 aci: Aci,
104 pni: Pni,
105 redemption_time: Timestamp,
106 public_key: &CredentialPublicKey,
107 ) -> Result<AuthCredentialWithPniZkc, ZkGroupVerificationFailure> {
108 if !redemption_time.is_day_aligned() {
109 return Err(ZkGroupVerificationFailure);
110 }
111
112 let aci = UidStruct::from_service_id(aci.into());
113 let pni = UidStruct::from_service_id(pni.into());
114
115 let raw_credential = zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
116 .add_attribute(&aci)
117 .add_attribute(&pni)
118 .add_public_attribute(&redemption_time)
119 .verify(public_key, self.proof)?;
120
121 Ok(AuthCredentialWithPniZkc {
122 credential: raw_credential,
123 version: VersionByte,
124 aci,
125 pni,
126 redemption_time,
127 })
128 }
129}
130
131impl AuthCredentialWithPniZkc {
132 pub fn present(
133 &self,
134 public_params: &ServerPublicParams,
135 group_secret_params: &GroupSecretParams,
136 randomness: RandomnessBytes,
137 ) -> AuthCredentialWithPniZkcPresentation {
138 self.present_for_key(
139 &public_params.generic_credential_public_key,
140 group_secret_params,
141 randomness,
142 )
143 }
144
145 pub(crate) fn present_for_key(
146 &self,
147 public_key: &CredentialPublicKey,
148 group_secret_params: &GroupSecretParams,
149 randomness: RandomnessBytes,
150 ) -> AuthCredentialWithPniZkcPresentation {
151 let Self {
152 aci,
153 credential,
154 pni,
155 redemption_time,
156 version: _,
157 } = self;
158
159 let proof = zkcredential::presentation::PresentationProofBuilder::new(CREDENTIAL_LABEL)
160 .add_attribute(aci, &group_secret_params.uid_enc_key_pair)
161 .add_attribute(pni, &group_secret_params.uid_enc_key_pair)
162 .present(public_key, credential, randomness);
163
164 AuthCredentialWithPniZkcPresentation {
165 aci_ciphertext: group_secret_params.uid_enc_key_pair.encrypt(&self.aci),
166 pni_ciphertext: group_secret_params.uid_enc_key_pair.encrypt(&self.pni),
167 proof,
168 redemption_time: *redemption_time,
169 version: VersionByte,
170 }
171 }
172}
173
174impl AuthCredentialWithPniZkcPresentation {
175 pub fn verify(
176 &self,
177 params: &ServerSecretParams,
178 group_public_params: &GroupPublicParams,
179 redemption_time: Timestamp,
180 ) -> Result<(), ZkGroupVerificationFailure> {
181 self.verify_for_key(
182 ¶ms.generic_credential_key_pair,
183 group_public_params,
184 redemption_time,
185 )
186 }
187
188 pub(crate) fn verify_for_key(
189 &self,
190 credential_key: &CredentialKeyPair,
191 group_public_params: &GroupPublicParams,
192 redemption_time: Timestamp,
193 ) -> Result<(), ZkGroupVerificationFailure> {
194 zkcredential::presentation::PresentationProofVerifier::new(CREDENTIAL_LABEL)
195 .add_attribute(
196 &self.aci_ciphertext,
197 &group_public_params.uid_enc_public_key,
198 )
199 .add_attribute(
200 &self.pni_ciphertext,
201 &group_public_params.uid_enc_public_key,
202 )
203 .add_public_attribute(&redemption_time)
204 .verify(credential_key, &self.proof)
205 .map_err(|_| ZkGroupVerificationFailure)
206 }
207
208 pub fn aci_ciphertext(&self) -> UuidCiphertext {
209 UuidCiphertext {
210 reserved: Default::default(),
211 ciphertext: self.aci_ciphertext,
212 }
213 }
214
215 pub fn pni_ciphertext(&self) -> UuidCiphertext {
216 UuidCiphertext {
217 reserved: Default::default(),
218 ciphertext: self.pni_ciphertext,
219 }
220 }
221
222 pub fn redemption_time(&self) -> Timestamp {
223 self.redemption_time
224 }
225}
226
227#[cfg(test)]
228mod test {
229 use zkcredential::RANDOMNESS_LEN;
230
231 use super::*;
232 use crate::SECONDS_PER_DAY;
233
234 #[test]
235 fn issue_receive_present() {
236 const ACI: Aci = Aci::from_uuid_bytes([b'a'; 16]);
237 const PNI: Pni = Pni::from_uuid_bytes([b'p'; 16]);
238 const REDEMPTION_TIME: Timestamp = Timestamp::from_epoch_seconds(12345 * SECONDS_PER_DAY);
239
240 let credential_key = CredentialKeyPair::generate([1; RANDOMNESS_LEN]);
241 let public_key = credential_key.public_key();
242 let group_secret_params = GroupSecretParams::generate([2; RANDOMNESS_LEN]);
243
244 let response = AuthCredentialWithPniZkcResponse::issue_credential_for_key(
245 ACI,
246 PNI,
247 REDEMPTION_TIME,
248 &credential_key,
249 [3; RANDOMNESS_LEN],
250 );
251
252 let credential = response
253 .receive_for_key(ACI, PNI, REDEMPTION_TIME, public_key)
254 .expect("is valid");
255
256 let presentation =
257 credential.present_for_key(public_key, &group_secret_params, [4; RANDOMNESS_LEN]);
258
259 presentation
260 .verify_for_key(
261 &credential_key,
262 &group_secret_params.get_public_params(),
263 REDEMPTION_TIME,
264 )
265 .expect("can verify")
266 }
267}