world_id_core/
credential.rs1#[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
9pub trait HashableCredential {
11 fn claims_hash(&self) -> Result<FieldElement, eyre::Error>;
17
18 fn hash(&self) -> Result<FieldElement, eyre::Error>;
26
27 #[cfg(feature = "issuer")]
32 fn sign(self, signer: &EdDSAPrivateKey) -> Result<Self, eyre::Error>
33 where
34 Self: Sized;
35
36 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}