1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//
// Copyright 2023 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//

//! Provides CallLinkAuthCredential and related types.
//!
//! CreateCallLinkCredential is a MAC over:
//! - the user's ACI (provided by the chat server at issuance, passed encrypted to the calling server for verification)
//! - a "redemption time", truncated to day granularity (chosen by the chat server at issuance based on parameters from the client, passed publicly to the calling server for verification)

use partial_default::PartialDefault;
use serde::{Deserialize, Serialize};

use super::{CallLinkPublicParams, CallLinkSecretParams};
use crate::common::serialization::ReservedByte;
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;

const CREDENTIAL_LABEL: &[u8] = b"20230421_Signal_CallLinkAuthCredential";

#[derive(Serialize, Deserialize, PartialDefault)]
pub struct CallLinkAuthCredentialResponse {
    reserved: ReservedByte,
    proof: zkcredential::issuance::IssuanceProof,
    // Does not include the user ID because the client already knows that.
    // Does not include the redemption time because that is passed externally.
}

impl CallLinkAuthCredentialResponse {
    pub fn issue_credential(
        user_id: libsignal_core::Aci,
        redemption_time: Timestamp,
        params: &GenericServerSecretParams,
        randomness: RandomnessBytes,
    ) -> CallLinkAuthCredentialResponse {
        let proof = zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
            .add_attribute(&UidStruct::from_service_id(user_id.into()))
            .add_public_attribute(&redemption_time)
            .issue(&params.credential_key, randomness);
        Self {
            reserved: Default::default(),
            proof,
        }
    }

    pub fn receive(
        self,
        user_id: libsignal_core::Aci,
        redemption_time: Timestamp,
        params: &GenericServerPublicParams,
    ) -> Result<CallLinkAuthCredential, ZkGroupVerificationFailure> {
        if !redemption_time.is_day_aligned() {
            return Err(ZkGroupVerificationFailure);
        }

        let raw_credential = zkcredential::issuance::IssuanceProofBuilder::new(CREDENTIAL_LABEL)
            .add_attribute(&UidStruct::from_service_id(user_id.into()))
            .add_public_attribute(&redemption_time)
            .verify(&params.credential_key, self.proof)
            .map_err(|_| ZkGroupVerificationFailure)?;
        Ok(CallLinkAuthCredential {
            reserved: Default::default(),
            credential: raw_credential,
        })
    }
}

#[derive(Serialize, Deserialize, PartialDefault)]
pub struct CallLinkAuthCredential {
    reserved: ReservedByte,
    credential: zkcredential::credentials::Credential,
    // Does not include the user ID because the client already knows that.
    // Does not include the redemption time because that's used as a key to lookup up this credential.
}

impl CallLinkAuthCredential {
    pub fn present(
        &self,
        user_id: libsignal_core::Aci,
        redemption_time: Timestamp,
        server_params: &GenericServerPublicParams,
        call_link_params: &CallLinkSecretParams,
        randomness: RandomnessBytes,
    ) -> CallLinkAuthCredentialPresentation {
        let uid_attr = UidStruct::from_service_id(user_id.into());
        let proof = zkcredential::presentation::PresentationProofBuilder::new(CREDENTIAL_LABEL)
            .add_attribute(&uid_attr, &call_link_params.uid_enc_key_pair)
            .present(&server_params.credential_key, &self.credential, randomness);
        CallLinkAuthCredentialPresentation {
            reserved: Default::default(),
            proof,
            ciphertext: call_link_params.uid_enc_key_pair.encrypt(&uid_attr),
            redemption_time,
        }
    }
}

#[derive(Serialize, Deserialize, PartialDefault)]
pub struct CallLinkAuthCredentialPresentation {
    reserved: ReservedByte,
    pub(crate) proof: zkcredential::presentation::PresentationProof,
    pub(crate) ciphertext: uid_encryption::Ciphertext,
    pub(crate) redemption_time: Timestamp,
}

impl CallLinkAuthCredentialPresentation {
    pub fn verify(
        &self,
        current_time: Timestamp,
        server_params: &GenericServerSecretParams,
        call_link_params: &CallLinkPublicParams,
    ) -> Result<(), ZkGroupVerificationFailure> {
        crate::ServerSecretParams::check_auth_credential_redemption_time(
            self.redemption_time,
            current_time,
        )?;

        zkcredential::presentation::PresentationProofVerifier::new(CREDENTIAL_LABEL)
            .add_attribute(&self.ciphertext, &call_link_params.uid_enc_public_key)
            .add_public_attribute(&self.redemption_time)
            .verify(&server_params.credential_key, &self.proof)
            .map_err(|_| ZkGroupVerificationFailure)
    }

    pub fn get_user_id(&self) -> UuidCiphertext {
        UuidCiphertext {
            reserved: Default::default(),
            ciphertext: self.ciphertext,
        }
    }
}