world_id_core/
credential.rs

1#[cfg(feature = "issuer")]
2use crate::EdDSAPrivateKey;
3use crate::EdDSAPublicKey;
4use eyre::bail;
5use poseidon2::{Poseidon2, POSEIDON2_BN254_T16_PARAMS};
6
7use crate::{Credential, CredentialVersion, FieldElement};
8
9/// Introduces hashing and signing capabilities to the `Credential` type.
10pub trait HashableCredential {
11    /// Get the claims hash of the credential.
12    ///
13    /// # Errors
14    /// Will error if there are more claims than the maximum allowed.
15    /// Will error if the claims cannot be lowered into the field. Should not occur in practice.
16    fn claims_hash(&self) -> Result<FieldElement, eyre::Error>;
17
18    // Computes the specifically designed hash of the credential for the given version.
19    ///
20    /// The hash is signed by the issuer to provide authenticity for the credential.
21    ///
22    /// # Errors
23    /// - Will error if there are more claims than the maximum allowed.
24    /// - Will error if the claims cannot be lowered into the field. Should not occur in practice.
25    fn hash(&self) -> Result<FieldElement, eyre::Error>;
26
27    /// Sign the credential.
28    ///
29    /// # Errors
30    /// Will error if the credential cannot be hashed.
31    #[cfg(feature = "issuer")]
32    fn sign(self, signer: &EdDSAPrivateKey) -> Result<Self, eyre::Error>
33    where
34        Self: Sized;
35
36    /// Verify the signature of the credential against the issuer public key and expected hash.
37    ///
38    /// # Errors
39    /// Will error if the credential is not signed.
40    /// Will error if the credential cannot be hashed.
41    fn verify_signature(
42        &self,
43        expected_issuer_pubkey: &EdDSAPublicKey,
44    ) -> Result<bool, eyre::Error>;
45}
46
47impl HashableCredential for Credential {
48    fn claims_hash(&self) -> Result<FieldElement, eyre::Error> {
49        let hasher = Poseidon2::new(&POSEIDON2_BN254_T16_PARAMS);
50        if self.claims.len() > Self::MAX_CLAIMS {
51            bail!("There can be at most {} claims", Self::MAX_CLAIMS);
52        }
53        let mut input = [*FieldElement::ZERO; Self::MAX_CLAIMS];
54        for (i, claim) in self.claims.iter().enumerate() {
55            input[i] = **claim;
56        }
57        hasher.permutation_in_place(&mut input);
58        Ok(input[1].into())
59    }
60
61    fn hash(&self) -> Result<FieldElement, eyre::Error> {
62        match self.version {
63            CredentialVersion::V1 => {
64                let hasher = Poseidon2::<_, 8, 5>::default();
65                let mut input = [
66                    *self.get_cred_ds(),
67                    self.issuer_schema_id.into(),
68                    *self.sub,
69                    self.genesis_issued_at.into(),
70                    self.expires_at.into(),
71                    *self.claims_hash()?,
72                    *self.associated_data_hash,
73                    self.id.into(),
74                ];
75                hasher.permutation_in_place(&mut input);
76                Ok(input[1].into())
77            }
78        }
79    }
80
81    #[cfg(feature = "issuer")]
82    fn sign(self, signer: &EdDSAPrivateKey) -> Result<Self, eyre::Error> {
83        let mut credential = self;
84        credential.signature = Some(signer.sign(*credential.hash()?));
85        credential.issuer = signer.public();
86        Ok(credential)
87    }
88
89    fn verify_signature(
90        &self,
91        expected_issuer_pubkey: &EdDSAPublicKey,
92    ) -> Result<bool, eyre::Error> {
93        if &self.issuer != expected_issuer_pubkey {
94            return Err(eyre::eyre!(
95                "Issuer public key does not match expected public key"
96            ));
97        }
98        if let Some(signature) = &self.signature {
99            return Ok(self.issuer.verify(*self.hash()?, signature));
100        }
101        Err(eyre::eyre!("Credential not signed"))
102    }
103}
104
105#[cfg(feature = "issuer")]
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use ruint::aliases::U256;
110
111    #[allow(clippy::unreadable_literal)]
112    #[test]
113    fn test_credential_builder_and_json_export() {
114        let credential = Credential::new()
115            .version(CredentialVersion::V1)
116            .issuer_schema_id(123)
117            .sub(456, FieldElement::random(&mut rand::thread_rng()))
118            .genesis_issued_at(1234567890)
119            .expires_at(1234567890 + 86_400)
120            .claim_hash(0, U256::from(999))
121            .unwrap()
122            .associated_data_hash(U256::from(42))
123            .unwrap();
124
125        let issuer_sk = EdDSAPrivateKey::from_bytes([0; 32]);
126        let credential = credential.sign(&issuer_sk).unwrap();
127
128        assert!(credential.signature.is_some());
129
130        let json = serde_json::to_string(&credential).unwrap();
131
132        let parsed: Credential = serde_json::from_str(&json).unwrap();
133        let json2 = serde_json::to_string(&parsed).unwrap();
134
135        assert_eq!(json, json2);
136
137        let issuer_public_key = issuer_sk.public();
138        let verified = issuer_public_key.verify(
139            *credential.hash().unwrap(),
140            credential.signature.as_ref().unwrap(),
141        );
142        assert!(verified);
143
144        let verified = credential.verify_signature(&issuer_public_key).unwrap();
145        assert!(verified);
146    }
147}