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