use libsignal_core::{Aci, Pni};
use partial_default::PartialDefault;
use serde::{Deserialize, Serialize};
use zkcredential::credentials::{CredentialKeyPair, CredentialPublicKey};
use crate::api::auth::auth_credential_with_pni::AuthCredentialWithPniVersion;
use crate::common::constants::PRESENTATION_VERSION_4;
use crate::common::serialization::VersionByte;
use crate::common::simple_types::{RandomnessBytes, Timestamp};
use crate::crypto::uid_encryption;
use crate::crypto::uid_struct::UidStruct;
use crate::groups::{GroupPublicParams, GroupSecretParams, UuidCiphertext};
use crate::{ServerPublicParams, ServerSecretParams, ZkGroupVerificationFailure};
const CREDENTIAL_LABEL: &[u8] = b"20240222_Signal_AuthCredentialZkc";
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
pub struct AuthCredentialWithPniZkc {
version: VersionByte<{ AuthCredentialWithPniVersion::Zkc as u8 }>,
credential: zkcredential::credentials::Credential,
aci: UidStruct,
pni: UidStruct,
redemption_time: Timestamp,
}
#[derive(Clone, Serialize, Deserialize, PartialDefault)]
pub struct AuthCredentialWithPniZkcResponse {
version: VersionByte<{ AuthCredentialWithPniVersion::Zkc as u8 }>,
proof: zkcredential::issuance::IssuanceProof,
}
#[derive(Serialize, Deserialize, PartialDefault)]
pub struct AuthCredentialWithPniZkcPresentation {
version: VersionByte<PRESENTATION_VERSION_4>,
proof: zkcredential::presentation::PresentationProof,
aci_ciphertext: uid_encryption::Ciphertext,
pni_ciphertext: uid_encryption::Ciphertext,
redemption_time: Timestamp,
}
impl AuthCredentialWithPniZkcResponse {
pub fn issue_credential(
aci: Aci,
pni: Pni,
redemption_time: Timestamp,
params: &ServerSecretParams,
randomness: RandomnessBytes,
) -> Self {
Self::issue_credential_for_key(
aci,
pni,
redemption_time,
¶ms.generic_credential_key_pair,
randomness,
)
}
pub fn receive(
self,
aci: Aci,
pni: Pni,
redemption_time: Timestamp,
public_params: &ServerPublicParams,
) -> Result<AuthCredentialWithPniZkc, ZkGroupVerificationFailure> {
self.receive_for_key(
aci,
pni,
redemption_time,
&public_params.generic_credential_public_key,
)
}
pub(crate) fn issue_credential_for_key(
aci: Aci,
pni: Pni,
redemption_time: Timestamp,
credential_key: &CredentialKeyPair,
randomness: RandomnessBytes,
) -> Self {
let proof = zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
.add_attribute(&UidStruct::from_service_id(aci.into()))
.add_attribute(&UidStruct::from_service_id(pni.into()))
.add_public_attribute(&redemption_time)
.issue(credential_key, randomness);
Self {
version: VersionByte,
proof,
}
}
pub(crate) fn receive_for_key(
self,
aci: Aci,
pni: Pni,
redemption_time: Timestamp,
public_key: &CredentialPublicKey,
) -> Result<AuthCredentialWithPniZkc, ZkGroupVerificationFailure> {
if !redemption_time.is_day_aligned() {
return Err(ZkGroupVerificationFailure);
}
let aci = UidStruct::from_service_id(aci.into());
let pni = UidStruct::from_service_id(pni.into());
let raw_credential = zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
.add_attribute(&aci)
.add_attribute(&pni)
.add_public_attribute(&redemption_time)
.verify(public_key, self.proof)?;
Ok(AuthCredentialWithPniZkc {
credential: raw_credential,
version: VersionByte,
aci,
pni,
redemption_time,
})
}
}
impl AuthCredentialWithPniZkc {
pub fn present(
&self,
public_params: &ServerPublicParams,
group_secret_params: &GroupSecretParams,
randomness: RandomnessBytes,
) -> AuthCredentialWithPniZkcPresentation {
self.present_for_key(
&public_params.generic_credential_public_key,
group_secret_params,
randomness,
)
}
pub(crate) fn present_for_key(
&self,
public_key: &CredentialPublicKey,
group_secret_params: &GroupSecretParams,
randomness: RandomnessBytes,
) -> AuthCredentialWithPniZkcPresentation {
let Self {
aci,
credential,
pni,
redemption_time,
version: _,
} = self;
let proof = zkcredential::presentation::PresentationProofBuilder::new(CREDENTIAL_LABEL)
.add_attribute(aci, &group_secret_params.uid_enc_key_pair)
.add_attribute(pni, &group_secret_params.uid_enc_key_pair)
.present(public_key, credential, randomness);
AuthCredentialWithPniZkcPresentation {
aci_ciphertext: group_secret_params.uid_enc_key_pair.encrypt(&self.aci),
pni_ciphertext: group_secret_params.uid_enc_key_pair.encrypt(&self.pni),
proof,
redemption_time: *redemption_time,
version: VersionByte,
}
}
}
impl AuthCredentialWithPniZkcPresentation {
pub fn verify(
&self,
params: &ServerSecretParams,
group_public_params: &GroupPublicParams,
redemption_time: Timestamp,
) -> Result<(), ZkGroupVerificationFailure> {
self.verify_for_key(
¶ms.generic_credential_key_pair,
group_public_params,
redemption_time,
)
}
pub(crate) fn verify_for_key(
&self,
credential_key: &CredentialKeyPair,
group_public_params: &GroupPublicParams,
redemption_time: Timestamp,
) -> Result<(), ZkGroupVerificationFailure> {
zkcredential::presentation::PresentationProofVerifier::new(CREDENTIAL_LABEL)
.add_attribute(
&self.aci_ciphertext,
&group_public_params.uid_enc_public_key,
)
.add_attribute(
&self.pni_ciphertext,
&group_public_params.uid_enc_public_key,
)
.add_public_attribute(&redemption_time)
.verify(credential_key, &self.proof)
.map_err(|_| ZkGroupVerificationFailure)
}
pub fn aci_ciphertext(&self) -> UuidCiphertext {
UuidCiphertext {
reserved: Default::default(),
ciphertext: self.aci_ciphertext,
}
}
pub fn pni_ciphertext(&self) -> UuidCiphertext {
UuidCiphertext {
reserved: Default::default(),
ciphertext: self.pni_ciphertext,
}
}
pub fn redemption_time(&self) -> Timestamp {
self.redemption_time
}
}
#[cfg(test)]
mod test {
use zkcredential::RANDOMNESS_LEN;
use super::*;
use crate::SECONDS_PER_DAY;
#[test]
fn issue_receive_present() {
const ACI: Aci = Aci::from_uuid_bytes([b'a'; 16]);
const PNI: Pni = Pni::from_uuid_bytes([b'p'; 16]);
const REDEMPTION_TIME: Timestamp = Timestamp::from_epoch_seconds(12345 * SECONDS_PER_DAY);
let credential_key = CredentialKeyPair::generate([1; RANDOMNESS_LEN]);
let public_key = credential_key.public_key();
let group_secret_params = GroupSecretParams::generate([2; RANDOMNESS_LEN]);
let response = AuthCredentialWithPniZkcResponse::issue_credential_for_key(
ACI,
PNI,
REDEMPTION_TIME,
&credential_key,
[3; RANDOMNESS_LEN],
);
let credential = response
.receive_for_key(ACI, PNI, REDEMPTION_TIME, public_key)
.expect("is valid");
let presentation =
credential.present_for_key(public_key, &group_secret_params, [4; RANDOMNESS_LEN]);
presentation
.verify_for_key(
&credential_key,
&group_secret_params.get_public_params(),
REDEMPTION_TIME,
)
.expect("can verify")
}
}