zkgroup/api/auth/auth_credential_with_pni/
zkc.rs

1//
2// Copyright 2024 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6use 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/// Authentication credential implemented using [`zkcredential`].
23///
24/// The same credential as [`crate::api::auth::AuthCredentialWithPni`] but
25/// implemented using the types and mechanism from the `zkcredential` crate.
26#[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            &params.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            &params.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}