1use std::{
6 hash::{Hash, Hasher},
7 sync::Arc,
8};
9
10use fastcrypto::{
11 error::FastCryptoError,
12 hash::{HashFunction, Sha256},
13 rsa::{Base64UrlUnpadded, Encoding},
14 secp256r1::{Secp256r1PublicKey, Secp256r1Signature},
15 traits::{ToFromBytes, VerifyingKey},
16};
17use once_cell::sync::OnceCell;
18use passkey_types::webauthn::{ClientDataType, CollectedClientData};
19use schemars::JsonSchema;
20use serde::{Deserialize, Deserializer, Serialize};
21use shared_crypto::intent::{INTENT_PREFIX_LENGTH, Intent, IntentMessage};
22
23use crate::{
24 base_types::{EpochId, IotaAddress},
25 crypto::{
26 DefaultHash, IotaSignature, IotaSignatureInner, PublicKey, Secp256r1IotaSignature,
27 Signature, SignatureScheme,
28 },
29 digests::ZKLoginInputsDigest,
30 error::{IotaError, IotaResult},
31 signature::{AuthenticatorTrait, VerifyParams},
32 signature_verification::VerifiedDigestCache,
33};
34
35#[cfg(test)]
36#[path = "unit_tests/passkey_authenticator_test.rs"]
37mod passkey_authenticator_test;
38
39#[derive(Debug, Clone, JsonSchema)]
42pub struct PasskeyAuthenticator {
43 authenticator_data: Vec<u8>,
48
49 client_data_json: String,
54
55 #[serde(skip)]
58 signature: Secp256r1Signature,
59
60 #[serde(skip)]
63 pk: Secp256r1PublicKey,
64
65 #[serde(skip)]
68 intent: Intent,
69
70 #[serde(skip)]
73 digest: [u8; DefaultHash::OUTPUT_SIZE],
74
75 #[serde(skip)]
77 bytes: OnceCell<Vec<u8>>,
78}
79
80#[derive(Serialize, Deserialize, Debug)]
83pub struct RawPasskeyAuthenticator {
84 pub authenticator_data: Vec<u8>,
85 pub client_data_json: String,
86 pub user_signature: Signature,
87}
88
89impl TryFrom<RawPasskeyAuthenticator> for PasskeyAuthenticator {
92 type Error = IotaError;
93
94 fn try_from(raw: RawPasskeyAuthenticator) -> Result<Self, Self::Error> {
95 let client_data_json_parsed: CollectedClientData =
96 serde_json::from_str(&raw.client_data_json).map_err(|_| {
97 IotaError::InvalidSignature {
98 error: "Invalid client data json".to_string(),
99 }
100 })?;
101
102 if client_data_json_parsed.ty != ClientDataType::Get {
103 return Err(IotaError::InvalidSignature {
104 error: "Invalid client data type".to_string(),
105 });
106 };
107
108 let parsed_challenge = Base64UrlUnpadded::decode_vec(&client_data_json_parsed.challenge)
109 .map_err(|_| IotaError::InvalidSignature {
110 error: "Invalid encoded challenge".to_string(),
111 })?;
112
113 let intent =
114 Intent::from_bytes(&parsed_challenge[..INTENT_PREFIX_LENGTH]).map_err(|_| {
115 IotaError::InvalidSignature {
116 error: "Invalid intent from challenge".to_string(),
117 }
118 })?;
119
120 let digest = parsed_challenge[INTENT_PREFIX_LENGTH..]
121 .try_into()
122 .map_err(|_| IotaError::InvalidSignature {
123 error: "Invalid digest from challenge".to_string(),
124 })?;
125
126 if raw.user_signature.scheme() != SignatureScheme::Secp256r1 {
127 return Err(IotaError::InvalidSignature {
128 error: "Invalid signature scheme".to_string(),
129 });
130 };
131
132 let pk = Secp256r1PublicKey::from_bytes(raw.user_signature.public_key_bytes()).map_err(
133 |_| IotaError::InvalidSignature {
134 error: "Invalid r1 pk".to_string(),
135 },
136 )?;
137
138 let signature = Secp256r1Signature::from_bytes(raw.user_signature.signature_bytes())
139 .map_err(|_| IotaError::InvalidSignature {
140 error: "Invalid r1 sig".to_string(),
141 })?;
142
143 Ok(PasskeyAuthenticator {
144 authenticator_data: raw.authenticator_data,
145 client_data_json: raw.client_data_json,
146 signature,
147 pk,
148 intent,
149 digest,
150 bytes: OnceCell::new(),
151 })
152 }
153}
154
155impl<'de> Deserialize<'de> for PasskeyAuthenticator {
156 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
157 where
158 D: Deserializer<'de>,
159 {
160 use serde::de::Error;
161
162 let serializable = RawPasskeyAuthenticator::deserialize(deserializer)?;
163 serializable
164 .try_into()
165 .map_err(|e: IotaError| Error::custom(e.to_string()))
166 }
167}
168
169impl Serialize for PasskeyAuthenticator {
170 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
171 where
172 S: serde::ser::Serializer,
173 {
174 let mut bytes = Vec::with_capacity(Secp256r1IotaSignature::LENGTH);
175 bytes.push(SignatureScheme::Secp256r1.flag());
176 bytes.extend_from_slice(self.signature.as_ref());
177 bytes.extend_from_slice(self.pk.as_ref());
178
179 let raw = RawPasskeyAuthenticator {
180 authenticator_data: self.authenticator_data.clone(),
181 client_data_json: self.client_data_json.clone(),
182 user_signature: Signature::Secp256r1IotaSignature(
183 Secp256r1IotaSignature::from_bytes(&bytes).unwrap(),
184 ),
185 };
186 raw.serialize(serializer)
187 }
188}
189impl PasskeyAuthenticator {
190 pub fn new_for_testing(
193 authenticator_data: Vec<u8>,
194 client_data_json: String,
195 user_signature: Signature,
196 ) -> Result<Self, IotaError> {
197 let raw = RawPasskeyAuthenticator {
198 authenticator_data,
199 client_data_json,
200 user_signature,
201 };
202 raw.try_into()
203 }
204
205 pub fn get_pk(&self) -> IotaResult<PublicKey> {
207 Ok(PublicKey::Passkey((&self.pk).into()))
208 }
209}
210
211impl PartialEq for PasskeyAuthenticator {
213 fn eq(&self, other: &Self) -> bool {
214 self.as_ref() == other.as_ref()
215 }
216}
217
218impl Eq for PasskeyAuthenticator {}
220
221impl Hash for PasskeyAuthenticator {
223 fn hash<H: Hasher>(&self, state: &mut H) {
224 self.as_ref().hash(state);
225 }
226}
227
228impl AuthenticatorTrait for PasskeyAuthenticator {
229 fn verify_user_authenticator_epoch(
230 &self,
231 _epoch: EpochId,
232 _max_epoch_upper_bound_delta: Option<u64>,
233 ) -> IotaResult {
234 Ok(())
235 }
236
237 fn verify_claims<T>(
239 &self,
240 intent_msg: &IntentMessage<T>,
241 author: IotaAddress,
242 _aux_verify_data: &VerifyParams,
243 _zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
244 ) -> IotaResult
245 where
246 T: Serialize,
247 {
248 if intent_msg.intent != self.intent || to_signing_digest(intent_msg) != self.digest {
251 return Err(IotaError::InvalidSignature {
252 error: "Invalid challenge".to_string(),
253 });
254 };
255
256 let mut message = self.authenticator_data.clone();
258 let client_data_hash = Sha256::digest(self.client_data_json.as_bytes()).digest;
259 message.extend_from_slice(&client_data_hash);
260
261 if author != IotaAddress::from(&self.get_pk()?) {
263 return Err(IotaError::InvalidSignature {
264 error: "Invalid author".to_string(),
265 });
266 };
267
268 self.pk
270 .verify(&message, &self.signature)
271 .map_err(|_| IotaError::InvalidSignature {
272 error: "Fails to verify".to_string(),
273 })
274 }
275}
276
277impl ToFromBytes for PasskeyAuthenticator {
278 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
279 if bytes.first().ok_or(FastCryptoError::InvalidInput)?
281 != &SignatureScheme::PasskeyAuthenticator.flag()
282 {
283 return Err(FastCryptoError::InvalidInput);
284 }
285 let passkey: PasskeyAuthenticator =
286 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
287 Ok(passkey)
288 }
289}
290
291impl AsRef<[u8]> for PasskeyAuthenticator {
292 fn as_ref(&self) -> &[u8] {
293 self.bytes
294 .get_or_try_init::<_, eyre::Report>(|| {
295 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
296 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
297 bytes.push(SignatureScheme::PasskeyAuthenticator.flag());
298 bytes.extend_from_slice(as_bytes.as_slice());
299 Ok(bytes)
300 })
301 .expect("OnceCell invariant violated")
302 }
303}
304pub fn to_signing_message<T: Serialize>(
307 intent_msg: &IntentMessage<T>,
308) -> [u8; INTENT_PREFIX_LENGTH + DefaultHash::OUTPUT_SIZE] {
309 let mut extended = [0; INTENT_PREFIX_LENGTH + DefaultHash::OUTPUT_SIZE];
310 extended[..INTENT_PREFIX_LENGTH].copy_from_slice(&intent_msg.intent.to_bytes());
311 extended[INTENT_PREFIX_LENGTH..].copy_from_slice(&to_signing_digest(intent_msg));
312 extended
313}
314
315pub fn to_signing_digest<T: Serialize>(
321 intent_msg: &IntentMessage<T>,
322) -> [u8; DefaultHash::OUTPUT_SIZE] {
323 let mut hasher = DefaultHash::default();
324 bcs::serialize_into(&mut hasher, &intent_msg.value)
325 .expect("Message serialization should not fail");
326 hasher.finalize().digest
327}