1use core::convert::Infallible;
4
5#[cfg(feature = "sys-rng")]
6use blind_rsa_signatures::reexports::rand::{rand_core::UnwrapErr, rngs::SysRng};
7#[cfg(not(feature = "sys-rng"))]
8use blind_rsa_signatures::DefaultRng;
9use blind_rsa_signatures::{
10 reexports::rand::rand_core::TryCryptoRng, BlindMessage, BlindingResult, MessageRandomizer,
11 Secret,
12};
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use serde_with::serde_as;
16
17use crate::serde::Base64UrlUnpadded;
18use crate::{
19 canonicalize_pbrsa_blind_msg, canonicalize_pbrsa_info,
20 verifier::{credential_holder_id, verify_credential_with_key},
21 Credential, CredentialProof, CredentialsError, HolderAuthorization, HolderAuthorizationRequest,
22 HolderId, IssuanceRequest, IssuanceResponse, IssuerId, PbrsaPublicKey, ProtocolV1,
23 SchnorrSignatureProof, SignedCredential, Timestamp,
24};
25
26fn default_pbrsa_rng() -> impl TryCryptoRng<Error = Infallible> {
27 #[cfg(feature = "sys-rng")]
28 {
29 UnwrapErr(SysRng)
30 }
31
32 #[cfg(not(feature = "sys-rng"))]
33 {
34 DefaultRng
35 }
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "snake_case")]
40enum PendingIssuanceStep {
41 WaitingForIssuerResponse,
42}
43
44#[derive(Clone)]
46pub struct HolderContext {
47 identity_keys: nostr::Keys,
48}
49
50impl HolderContext {
51 pub fn generate() -> Self {
53 Self::generate_with_rng(&mut nostr::secp256k1::rand::rngs::OsRng)
54 }
55
56 pub(crate) fn generate_with_rng(
57 rng: &mut (impl nostr::secp256k1::rand::Rng + nostr::secp256k1::rand::CryptoRng),
58 ) -> Self {
59 Self {
60 identity_keys: nostr::Keys::generate_with_rng(rng),
61 }
62 }
63
64 pub fn import_secret_key(secret_key: &str) -> Result<Self, CredentialsError> {
66 Ok(Self {
67 identity_keys: nostr::Keys::parse(secret_key)?,
68 })
69 }
70
71 pub fn export_secret_key(&self) -> String {
73 self.identity_keys.secret_key().to_secret_hex()
74 }
75
76 pub fn public_key(&self) -> nostr::PublicKey {
78 self.identity_keys.public_key()
79 }
80
81 pub fn holder_id(&self) -> HolderId {
83 HolderId(self.identity_keys.public_key())
84 }
85
86 pub fn authorize_credential_use(
93 &self,
94 request: HolderAuthorizationRequest,
95 credential: &SignedCredential,
96 ) -> Result<HolderAuthorization, CredentialsError> {
97 self.authorize_credential_use_at_time(request, credential, current_unix_timestamp()?)
98 }
99
100 pub fn authorize_credential_use_at_time(
102 &self,
103 request: HolderAuthorizationRequest,
104 credential: &SignedCredential,
105 issued_at: impl Into<Timestamp>,
106 ) -> Result<HolderAuthorization, CredentialsError> {
107 self.authorize_credential_use_with_rng_at_time(
108 request,
109 credential,
110 issued_at.into(),
111 &mut nostr::secp256k1::rand::rngs::OsRng,
112 )
113 }
114
115 pub(crate) fn authorize_credential_use_with_rng_at_time(
116 &self,
117 request: HolderAuthorizationRequest,
118 credential: &SignedCredential,
119 issued_at: impl Into<Timestamp>,
120 rng: &mut (impl nostr::secp256k1::rand::Rng + nostr::secp256k1::rand::CryptoRng),
121 ) -> Result<HolderAuthorization, CredentialsError> {
122 let holder_id = self.holder_id();
123 if credential_holder_id(credential)? != holder_id {
124 return Err(CredentialsError::HolderIdMismatch);
125 }
126
127 let authorization = request.into_statement(holder_id, issued_at.into(), credential)?;
128 let signature = self.sign_identity_digest_with_rng(authorization.digest()?, rng);
129
130 Ok(HolderAuthorization {
131 version: ProtocolV1,
132 authorization,
133 proof: SchnorrSignatureProof { signature },
134 })
135 }
136
137 fn sign_identity_digest_with_rng(
138 &self,
139 digest: sha2::digest::Output<sha2::Sha256>,
140 rng: &mut (impl nostr::secp256k1::rand::Rng + nostr::secp256k1::rand::CryptoRng),
141 ) -> nostr::secp256k1::schnorr::Signature {
142 self.identity_keys.sign_schnorr_with_ctx(
143 nostr::SECP256K1,
144 &nostr::secp256k1::Message::from_digest(digest.into()),
145 rng,
146 )
147 }
148}
149
150#[serde_as]
152#[derive(Serialize, Deserialize)]
153#[serde(deny_unknown_fields)]
154pub struct PendingIssuance {
155 pub version: ProtocolV1,
156 step: PendingIssuanceStep,
157 pub issuer_id: IssuerId,
158 pub info: Value,
159 pub blind_msg: Value,
160 #[serde_as(as = "Base64UrlUnpadded")]
161 blind_message: Vec<u8>,
162 #[serde_as(as = "Base64UrlUnpadded")]
163 secret: Vec<u8>,
164 #[serde_as(as = "Option<Base64UrlUnpadded>")]
165 msg_randomizer: Option<Vec<u8>>,
166}
167
168impl PendingIssuance {
169 pub fn create_request(
171 issuer_public_key: &PbrsaPublicKey,
172 issuer_id: IssuerId,
173 info: Value,
174 blind_msg: Value,
175 ) -> Result<(IssuanceRequest, Self), CredentialsError> {
176 let mut rng = default_pbrsa_rng();
177
178 Self::create_request_with_rng(issuer_public_key, issuer_id, info, blind_msg, &mut rng)
179 }
180
181 pub(crate) fn create_request_with_rng(
182 issuer_public_key: &PbrsaPublicKey,
183 issuer_id: IssuerId,
184 info: Value,
185 blind_msg: Value,
186 rng: &mut (impl blind_rsa_signatures::reexports::rsa::rand_core::CryptoRng + ?Sized),
187 ) -> Result<(IssuanceRequest, Self), CredentialsError> {
188 let metadata = canonicalize_pbrsa_info(ProtocolV1, &issuer_id, &info)?;
189 let message = canonicalize_pbrsa_blind_msg(ProtocolV1, &blind_msg)?;
190 let public_key = issuer_public_key.derive_public_key_for_metadata(&metadata)?;
191 let blinding_result = public_key.blind(rng, &message, Some(&metadata))?;
192 let blinded_message = blinding_result.blind_message.clone();
193
194 let request = IssuanceRequest {
195 version: ProtocolV1,
196 blinded_message,
197 };
198 let pending = Self {
199 version: ProtocolV1,
200 step: PendingIssuanceStep::WaitingForIssuerResponse,
201 issuer_id,
202 info,
203 blind_msg,
204 blind_message: blinding_result.blind_message.0,
205 secret: blinding_result.secret.0,
206 msg_randomizer: blinding_result
207 .msg_randomizer
208 .map(|msg_randomizer| msg_randomizer.0.to_vec()),
209 };
210
211 Ok((request, pending))
212 }
213
214 pub fn export_state(&self) -> Result<String, CredentialsError> {
219 Ok(serde_json::to_string(self)?)
220 }
221
222 pub fn import_state(state: &str) -> Result<Self, CredentialsError> {
224 serde_json::from_str(state).map_err(invalid_pending_issuance_state)
225 }
226
227 pub fn finalize(
229 self,
230 issuer_public_key: &PbrsaPublicKey,
231 response: &IssuanceResponse,
232 ) -> Result<SignedCredential, CredentialsError> {
233 if response.issuer_id != self.issuer_id {
234 return Err(CredentialsError::IssuerIdMismatch);
235 }
236 if response.info != self.info {
237 return Err(CredentialsError::InfoMismatch);
238 }
239
240 let metadata = canonicalize_pbrsa_info(ProtocolV1, &self.issuer_id, &self.info)?;
241 let message = canonicalize_pbrsa_blind_msg(ProtocolV1, &self.blind_msg)?;
242 let public_key = issuer_public_key.derive_public_key_for_metadata(&metadata)?;
243 let blinding_result = self.blinding_result()?;
244 let signature = public_key.finalize(
245 &response.blind_signature,
246 &blinding_result,
247 &message,
248 Some(&metadata),
249 )?;
250 let credential = SignedCredential {
251 version: ProtocolV1,
252 credential: Credential {
253 issuer_id_pubkey: self.issuer_id,
254 info: self.info,
255 blind_msg: self.blind_msg,
256 },
257 proof: CredentialProof { signature },
258 };
259 verify_credential_with_key(issuer_public_key, &credential)?;
260 Ok(credential)
261 }
262
263 fn blinding_result(&self) -> Result<BlindingResult, CredentialsError> {
264 let msg_randomizer = self
265 .msg_randomizer
266 .as_ref()
267 .map(|msg_randomizer| {
268 let msg_randomizer: [u8; 32] =
269 msg_randomizer.as_slice().try_into().map_err(|_| {
270 CredentialsError::InvalidPendingIssuanceState(format!(
271 "message randomizer must be 32 bytes, got {}",
272 msg_randomizer.len()
273 ))
274 })?;
275 Ok::<MessageRandomizer, CredentialsError>(MessageRandomizer(msg_randomizer))
276 })
277 .transpose()?;
278
279 Ok(BlindingResult {
280 blind_message: BlindMessage(self.blind_message.clone()),
281 secret: Secret(self.secret.clone()),
282 msg_randomizer,
283 })
284 }
285}
286
287fn invalid_pending_issuance_state(error: impl ToString) -> CredentialsError {
288 CredentialsError::InvalidPendingIssuanceState(error.to_string())
289}
290
291pub(crate) fn current_unix_timestamp() -> Result<Timestamp, CredentialsError> {
292 std::time::SystemTime::now()
293 .duration_since(std::time::UNIX_EPOCH)
294 .map(|duration| Timestamp(duration.as_secs()))
295 .map_err(|_| CredentialsError::VerificationFailed)
296}