Skip to main content

peerbadge_protocol/
verifier.rs

1//! Verifier-side PBRSA credential verification operations.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use crate::{
6    canonicalize_pbrsa_blind_msg, canonicalize_pbrsa_info, holder::current_unix_timestamp,
7    CredentialDigest, CredentialsError, HolderAuthorization, HolderId, IssuerAuthority, IssuerId,
8    PbrsaPublicKey, ProtocolV1, Revocation, SignedCredential, SignedRevocation, Timestamp,
9};
10
11/// Stateful verifier for trusted issuers, revocations, and credentials.
12#[derive(Clone, Default)]
13pub struct VerificationContext {
14    issuers: BTreeMap<IssuerId, PbrsaPublicKey>,
15    revocations: BTreeSet<(IssuerId, Revocation)>,
16}
17
18impl VerificationContext {
19    /// Create an empty verifier context.
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Verify and trust an issuer authority for subsequent credential checks.
25    pub fn add_issuer_authority(
26        &mut self,
27        authority: &IssuerAuthority,
28    ) -> Result<(), CredentialsError> {
29        let issuer = authority.verify()?;
30        self.issuers
31            .insert(issuer.issuer_id_pubkey.clone(), issuer.issuance_key.clone());
32
33        Ok(())
34    }
35
36    /// Verify and store a signed revocation from a trusted issuer.
37    pub fn add_revocation(
38        &mut self,
39        signed_revocation: &SignedRevocation,
40    ) -> Result<(), CredentialsError> {
41        let revocation = signed_revocation.verify()?;
42        if !self
43            .issuers
44            .contains_key(&signed_revocation.proof.issuer_id_pubkey)
45        {
46            return Err(CredentialsError::UnknownIssuer);
47        }
48
49        self.revocations
50            .insert((signed_revocation.proof.issuer_id_pubkey.clone(), revocation));
51        Ok(())
52    }
53
54    /// Verify a finalized credential against trusted issuers and revocations.
55    pub fn verify_credential(&self, credential: &SignedCredential) -> Result<(), CredentialsError> {
56        let issuer_public_key = self
57            .issuers
58            .get(&credential.credential.issuer_id_pubkey)
59            .ok_or(CredentialsError::UnknownIssuer)?;
60
61        verify_credential_with_key(issuer_public_key, credential)?;
62
63        let revocation = Revocation {
64            credential_digest: CredentialDigest(credential.credential.digest()?),
65        };
66
67        if self
68            .revocations
69            .contains(&(credential.credential.issuer_id_pubkey.clone(), revocation))
70        {
71            return Err(CredentialsError::CredentialRevoked);
72        }
73
74        Ok(())
75    }
76
77    /// Verify a credential and a holder authorization.
78    ///
79    /// The SDK verifies signatures, issuer trust, credential revocation state,
80    /// credential binding, holder binding, and the authorization issued-at time.
81    /// Application policy still owns live proof that the caller controls
82    /// `authorization.authorization.subject_pubkey`.
83    pub fn verify_credential_authorization(
84        &self,
85        credential: &SignedCredential,
86        authorization: &HolderAuthorization,
87    ) -> Result<(), CredentialsError> {
88        self.verify_credential_authorization_at_time(
89            credential,
90            authorization,
91            current_unix_timestamp()?,
92        )
93    }
94
95    /// Verify a credential and holder authorization using an explicit timestamp.
96    pub fn verify_credential_authorization_at_time(
97        &self,
98        credential: &SignedCredential,
99        authorization: &HolderAuthorization,
100        now: impl Into<Timestamp>,
101    ) -> Result<(), CredentialsError> {
102        self.verify_credential(credential)?;
103
104        let authorization = authorization.verify()?;
105        let now = now.into();
106        let credential_holder_id = credential_holder_id(credential)?;
107
108        if credential_holder_id != authorization.holder_id_pubkey {
109            return Err(CredentialsError::HolderIdMismatch);
110        }
111
112        if now < authorization.issued_at {
113            return Err(CredentialsError::AuthorizationNotYetValid);
114        }
115
116        let expected_credential_digest = CredentialDigest(credential.credential.digest()?);
117
118        if authorization.credential_digest != expected_credential_digest {
119            return Err(CredentialsError::AuthorizationCredentialDigestMismatch);
120        }
121
122        Ok(())
123    }
124}
125
126pub(crate) fn credential_holder_id(
127    credential: &SignedCredential,
128) -> Result<HolderId, CredentialsError> {
129    let Some(holder_id) = credential.credential.blind_msg.as_str() else {
130        return Err(CredentialsError::VerificationFailed);
131    };
132
133    holder_id
134        .parse::<HolderId>()
135        .map_err(|_| CredentialsError::VerificationFailed)
136}
137
138pub(crate) fn verify_credential_with_key(
139    issuer_public_key: &PbrsaPublicKey,
140    credential: &SignedCredential,
141) -> Result<(), CredentialsError> {
142    let metadata = canonicalize_pbrsa_info(
143        ProtocolV1,
144        &credential.credential.issuer_id_pubkey,
145        &credential.credential.info,
146    )?;
147    let message = canonicalize_pbrsa_blind_msg(ProtocolV1, &credential.credential.blind_msg)?;
148    let public_key = issuer_public_key.derive_public_key_for_metadata(&metadata)?;
149    public_key.verify(&credential.proof.signature, None, &message, Some(&metadata))?;
150    Ok(())
151}