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::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 challenge: [u8; DefaultHash::OUTPUT_SIZE],
69
70 #[serde(skip)]
72 bytes: OnceCell<Vec<u8>>,
73}
74
75#[derive(Serialize, Deserialize, Debug)]
78pub struct RawPasskeyAuthenticator {
79 pub authenticator_data: Vec<u8>,
80 pub client_data_json: String,
81 pub user_signature: Signature,
82}
83
84impl TryFrom<RawPasskeyAuthenticator> for PasskeyAuthenticator {
87 type Error = IotaError;
88
89 fn try_from(raw: RawPasskeyAuthenticator) -> Result<Self, Self::Error> {
90 let client_data_json_parsed: CollectedClientData =
91 serde_json::from_str(&raw.client_data_json).map_err(|_| {
92 IotaError::InvalidSignature {
93 error: "Invalid client data json".to_string(),
94 }
95 })?;
96
97 if client_data_json_parsed.ty != ClientDataType::Get {
98 return Err(IotaError::InvalidSignature {
99 error: "Invalid client data type".to_string(),
100 });
101 };
102
103 let challenge = Base64UrlUnpadded::decode_vec(&client_data_json_parsed.challenge)
104 .map_err(|_| IotaError::InvalidSignature {
105 error: "Invalid encoded challenge".to_string(),
106 })?
107 .try_into()
108 .map_err(|_| IotaError::InvalidSignature {
109 error: "Invalid size for challenge".to_string(),
110 })?;
111
112 if raw.user_signature.scheme() != SignatureScheme::Secp256r1 {
113 return Err(IotaError::InvalidSignature {
114 error: "Invalid signature scheme".to_string(),
115 });
116 };
117
118 let pk = Secp256r1PublicKey::from_bytes(raw.user_signature.public_key_bytes()).map_err(
119 |_| IotaError::InvalidSignature {
120 error: "Invalid r1 pk".to_string(),
121 },
122 )?;
123
124 let signature = Secp256r1Signature::from_bytes(raw.user_signature.signature_bytes())
125 .map_err(|_| IotaError::InvalidSignature {
126 error: "Invalid r1 sig".to_string(),
127 })?;
128
129 Ok(PasskeyAuthenticator {
130 authenticator_data: raw.authenticator_data,
131 client_data_json: raw.client_data_json,
132 signature,
133 pk,
134 challenge,
135 bytes: OnceCell::new(),
136 })
137 }
138}
139
140impl<'de> Deserialize<'de> for PasskeyAuthenticator {
141 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142 where
143 D: Deserializer<'de>,
144 {
145 use serde::de::Error;
146
147 let serializable = RawPasskeyAuthenticator::deserialize(deserializer)?;
148 serializable
149 .try_into()
150 .map_err(|e: IotaError| Error::custom(e.to_string()))
151 }
152}
153
154impl Serialize for PasskeyAuthenticator {
155 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156 where
157 S: serde::ser::Serializer,
158 {
159 let mut bytes = Vec::with_capacity(Secp256r1IotaSignature::LENGTH);
160 bytes.push(SignatureScheme::Secp256r1.flag());
161 bytes.extend_from_slice(self.signature.as_ref());
162 bytes.extend_from_slice(self.pk.as_ref());
163
164 let raw = RawPasskeyAuthenticator {
165 authenticator_data: self.authenticator_data.clone(),
166 client_data_json: self.client_data_json.clone(),
167 user_signature: Signature::Secp256r1IotaSignature(
168 Secp256r1IotaSignature::from_bytes(&bytes).unwrap(), ),
172 };
173 raw.serialize(serializer)
174 }
175}
176
177impl PasskeyAuthenticator {
178 pub fn new_for_testing(
181 authenticator_data: Vec<u8>,
182 client_data_json: String,
183 user_signature: Signature,
184 ) -> Result<Self, IotaError> {
185 let raw = RawPasskeyAuthenticator {
186 authenticator_data,
187 client_data_json,
188 user_signature,
189 };
190 raw.try_into()
191 }
192
193 pub fn get_pk(&self) -> IotaResult<PublicKey> {
195 Ok(PublicKey::Passkey((&self.pk).into()))
196 }
197
198 pub fn authenticator_data(&self) -> &[u8] {
199 &self.authenticator_data
200 }
201
202 pub fn client_data_json(&self) -> &str {
203 &self.client_data_json
204 }
205
206 pub fn signature(&self) -> Signature {
207 let mut bytes = Vec::with_capacity(Secp256r1IotaSignature::LENGTH);
208 bytes.push(SignatureScheme::Secp256r1.flag());
209 bytes.extend_from_slice(self.signature.as_ref());
210 bytes.extend_from_slice(self.pk.as_ref());
211
212 Signature::Secp256r1IotaSignature(Secp256r1IotaSignature::from_bytes(&bytes).unwrap())
214 }
215}
216
217impl PartialEq for PasskeyAuthenticator {
219 fn eq(&self, other: &Self) -> bool {
220 self.as_ref() == other.as_ref()
221 }
222}
223
224impl Eq for PasskeyAuthenticator {}
226
227impl Hash for PasskeyAuthenticator {
229 fn hash<H: Hasher>(&self, state: &mut H) {
230 self.as_ref().hash(state);
231 }
232}
233
234impl AuthenticatorTrait for PasskeyAuthenticator {
235 fn verify_user_authenticator_epoch(
236 &self,
237 _epoch: EpochId,
238 _max_epoch_upper_bound_delta: Option<u64>,
239 ) -> IotaResult {
240 Ok(())
241 }
242
243 fn verify_claims<T>(
245 &self,
246 intent_msg: &IntentMessage<T>,
247 author: IotaAddress,
248 _aux_verify_data: &VerifyParams,
249 _zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
250 ) -> IotaResult
251 where
252 T: Serialize,
253 {
254 if author != IotaAddress::from(&self.get_pk()?) {
256 return Err(IotaError::InvalidSignature {
257 error: "Invalid author".to_string(),
258 });
259 };
260
261 if self.challenge != to_signing_message(intent_msg) {
264 return Err(IotaError::InvalidSignature {
265 error: "Invalid challenge".to_string(),
266 });
267 };
268
269 let mut message = self.authenticator_data.clone();
271 let client_data_hash = Sha256::digest(self.client_data_json.as_bytes()).digest;
272 message.extend_from_slice(&client_data_hash);
273
274 self.pk
276 .verify(&message, &self.signature)
277 .map_err(|_| IotaError::InvalidSignature {
278 error: "Fails to verify".to_string(),
279 })
280 }
281}
282
283impl ToFromBytes for PasskeyAuthenticator {
284 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
285 if bytes.first().ok_or(FastCryptoError::InvalidInput)?
287 != &SignatureScheme::PasskeyAuthenticator.flag()
288 {
289 return Err(FastCryptoError::InvalidInput);
290 }
291 let passkey: PasskeyAuthenticator =
292 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
293 Ok(passkey)
294 }
295}
296
297impl AsRef<[u8]> for PasskeyAuthenticator {
298 fn as_ref(&self) -> &[u8] {
299 self.bytes
300 .get_or_try_init::<_, eyre::Report>(|| {
301 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
302 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
303 bytes.push(SignatureScheme::PasskeyAuthenticator.flag());
304 bytes.extend_from_slice(as_bytes.as_slice());
305 Ok(bytes)
306 })
307 .expect("OnceCell invariant violated")
308 }
309}
310
311pub fn to_signing_message<T: Serialize>(
314 intent_msg: &IntentMessage<T>,
315) -> [u8; DefaultHash::OUTPUT_SIZE] {
316 let mut hasher = DefaultHash::default();
317 bcs::serialize_into(&mut hasher, intent_msg).expect("Message serialization should not fail");
318 hasher.finalize().digest
319}