use curve25519_dalek_signal::ristretto::RistrettoPoint;
use partial_default::PartialDefault;
use poksho::ShoApi;
use serde::{Deserialize, Serialize};
use super::{CallLinkPublicParams, CallLinkSecretParams};
use crate::common::serialization::ReservedByte;
use crate::common::sho::Sho;
use crate::common::simple_types::*;
use crate::crypto::uid_encryption;
use crate::crypto::uid_struct::UidStruct;
use crate::generic_server_params::{GenericServerPublicParams, GenericServerSecretParams};
use crate::groups::UuidCiphertext;
use crate::ZkGroupVerificationFailure;
#[derive(Serialize, Deserialize, Clone, Copy)]
struct CallLinkRoomIdPoint(RistrettoPoint);
impl CallLinkRoomIdPoint {
fn new(room_id: &[u8]) -> Self {
Self(Sho::new(b"20230413_Signal_CallLinkRoomId", room_id).get_point())
}
}
impl zkcredential::attributes::RevealedAttribute for CallLinkRoomIdPoint {
fn as_point(&self) -> RistrettoPoint {
self.0
}
}
const CREDENTIAL_LABEL: &[u8] = b"20230413_Signal_CreateCallLinkCredential";
#[derive(Serialize, Deserialize, PartialDefault)]
pub struct CreateCallLinkCredentialRequestContext {
reserved: ReservedByte,
blinded_room_id: zkcredential::issuance::blind::BlindedPoint,
key_pair: zkcredential::issuance::blind::BlindingKeyPair,
}
impl CreateCallLinkCredentialRequestContext {
pub fn new(room_id: &[u8], randomness: RandomnessBytes) -> Self {
let mut sho =
poksho::ShoHmacSha256::new(b"20230413_Signal_CreateCallLinkCredentialRequest");
sho.absorb_and_ratchet(&randomness);
let key_pair = zkcredential::issuance::blind::BlindingKeyPair::generate(&mut sho);
let blinded_room_id = key_pair
.blind(&CallLinkRoomIdPoint::new(room_id), &mut sho)
.into();
Self {
reserved: Default::default(),
blinded_room_id,
key_pair,
}
}
pub fn get_request(&self) -> CreateCallLinkCredentialRequest {
CreateCallLinkCredentialRequest {
reserved: Default::default(),
blinded_room_id: self.blinded_room_id,
public_key: *self.key_pair.public_key(),
}
}
}
#[derive(Serialize, Deserialize, PartialDefault)]
pub struct CreateCallLinkCredentialRequest {
reserved: ReservedByte,
blinded_room_id: zkcredential::issuance::blind::BlindedPoint,
public_key: zkcredential::issuance::blind::BlindingPublicKey,
}
impl CreateCallLinkCredentialRequest {
pub fn issue(
&self,
user_id: libsignal_core::Aci,
timestamp: Timestamp,
params: &GenericServerSecretParams,
randomness: RandomnessBytes,
) -> CreateCallLinkCredentialResponse {
CreateCallLinkCredentialResponse {
reserved: Default::default(),
timestamp,
blinded_credential: zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
.add_public_attribute(×tamp)
.add_attribute(&UidStruct::from_service_id(user_id.into()))
.add_blinded_revealed_attribute(&self.blinded_room_id)
.issue(¶ms.credential_key, &self.public_key, randomness),
}
}
}
#[derive(Serialize, Deserialize, PartialDefault)]
pub struct CreateCallLinkCredentialResponse {
reserved: ReservedByte,
timestamp: Timestamp,
blinded_credential: zkcredential::issuance::blind::BlindedIssuanceProof,
}
impl CreateCallLinkCredentialRequestContext {
pub fn receive(
self,
response: CreateCallLinkCredentialResponse,
user_id: libsignal_core::Aci,
params: &GenericServerPublicParams,
) -> Result<CreateCallLinkCredential, ZkGroupVerificationFailure> {
if !response.timestamp.is_day_aligned() {
return Err(ZkGroupVerificationFailure);
}
Ok(CreateCallLinkCredential {
reserved: Default::default(),
timestamp: response.timestamp,
credential: zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
.add_public_attribute(&response.timestamp)
.add_attribute(&UidStruct::from_service_id(user_id.into()))
.add_blinded_revealed_attribute(&self.blinded_room_id)
.verify(
¶ms.credential_key,
&self.key_pair,
response.blinded_credential,
)
.map_err(|_| ZkGroupVerificationFailure)?,
})
}
}
#[derive(Serialize, Deserialize, PartialDefault)]
pub struct CreateCallLinkCredential {
reserved: ReservedByte,
timestamp: Timestamp,
credential: zkcredential::credentials::Credential,
}
impl CreateCallLinkCredential {
pub fn present(
&self,
room_id: &[u8],
user_id: libsignal_core::Aci,
server_params: &GenericServerPublicParams,
call_link_params: &CallLinkSecretParams,
randomness: RandomnessBytes,
) -> CreateCallLinkCredentialPresentation {
let user_id = UidStruct::from_service_id(user_id.into());
let encrypted_user_id = call_link_params.uid_enc_key_pair.encrypt(&user_id);
CreateCallLinkCredentialPresentation {
reserved: Default::default(),
timestamp: self.timestamp,
user_id: encrypted_user_id,
proof: zkcredential::presentation::PresentationProofBuilder::new(CREDENTIAL_LABEL)
.add_attribute(&user_id, &call_link_params.uid_enc_key_pair)
.add_revealed_attribute(&CallLinkRoomIdPoint::new(room_id))
.present(&server_params.credential_key, &self.credential, randomness),
}
}
}
#[derive(Serialize, Deserialize, PartialDefault)]
pub struct CreateCallLinkCredentialPresentation {
reserved: ReservedByte,
user_id: zkcredential::attributes::Ciphertext<uid_encryption::UidEncryptionDomain>,
timestamp: Timestamp,
proof: zkcredential::presentation::PresentationProof,
}
impl CreateCallLinkCredentialPresentation {
pub fn verify(
&self,
room_id: &[u8],
current_time: Timestamp,
server_params: &GenericServerSecretParams,
call_link_params: &CallLinkPublicParams,
) -> Result<(), ZkGroupVerificationFailure> {
let expiration = self
.timestamp
.checked_add_seconds(30 * 60 * 60) .ok_or(ZkGroupVerificationFailure)?;
if !(self.timestamp..expiration).contains(¤t_time) {
return Err(ZkGroupVerificationFailure);
}
zkcredential::presentation::PresentationProofVerifier::new(CREDENTIAL_LABEL)
.add_public_attribute(&self.timestamp)
.add_attribute(&self.user_id, &call_link_params.uid_enc_public_key)
.add_revealed_attribute(&CallLinkRoomIdPoint::new(room_id))
.verify(&server_params.credential_key, &self.proof)
.map_err(|_| ZkGroupVerificationFailure)
}
pub fn get_user_id(&self) -> UuidCiphertext {
UuidCiphertext {
reserved: Default::default(),
ciphertext: self.user_id,
}
}
}