1use crate::secp256r1::Secp256r1VerifyingKey;
2use crate::SignatureError;
3use signature::Verifier;
4use sui_sdk_types::PasskeyAuthenticator;
5use sui_sdk_types::SimpleSignature;
6use sui_sdk_types::UserSignature;
7
8#[derive(Default, Clone, Debug)]
9pub struct PasskeyVerifier {}
10
11impl PasskeyVerifier {
12 pub fn new() -> Self {
13 Self {}
14 }
15}
16
17impl Verifier<PasskeyAuthenticator> for PasskeyVerifier {
18 fn verify(
19 &self,
20 message: &[u8],
21 authenticator: &PasskeyAuthenticator,
22 ) -> Result<(), SignatureError> {
23 let SimpleSignature::Secp256r1 {
24 signature,
25 public_key,
26 } = authenticator.signature()
27 else {
28 return Err(SignatureError::from_source("not a secp256r1 signature"));
29 };
30
31 if message != authenticator.challenge() {
32 return Err(SignatureError::from_source(
33 "passkey challenge does not match expected message",
34 ));
35 }
36
37 let mut message = authenticator.authenticator_data().to_owned();
39 let client_data_hash = {
40 use sha2::Digest;
41
42 let mut hasher = sha2::Sha256::new();
43 hasher.update(authenticator.client_data_json().as_bytes());
44 hasher.finalize()
45 };
46 message.extend_from_slice(&client_data_hash);
47
48 let verifying_key = Secp256r1VerifyingKey::new(&public_key)?;
49
50 verifying_key.verify(&message, &signature)
51 }
52}
53
54impl Verifier<UserSignature> for PasskeyVerifier {
55 fn verify(&self, message: &[u8], signature: &UserSignature) -> Result<(), SignatureError> {
56 let UserSignature::Passkey(authenticator) = signature else {
57 return Err(SignatureError::from_source("not a passkey authenticator"));
58 };
59
60 <Self as Verifier<PasskeyAuthenticator>>::verify(self, message, authenticator)
61 }
62}
63
64#[cfg(test)]
65mod test {
66 use super::*;
67 use crate::SuiVerifier;
68 use sui_sdk_types::Transaction;
69
70 #[cfg(target_arch = "wasm32")]
71 use wasm_bindgen_test::wasm_bindgen_test as test;
72
73 #[test]
74 fn transaction_signing_fixture() {
75 let transaction = "AAAAACdZawPnpJRjmVcwDu6xrIumtq5NLO+6GHbs0iGdCoD7AQ0T0TolicYERdSvyCRjSSduDZLbSpBsZBoib+lF48EBcgAAAAAAAAAgpQr/Mudl9BdzyBdkbqTlqBw4/aJ21kAD/jpJKa05im4nWWsD56SUY5lXMA7usayLprauTSzvuhh27NIhnQqA++gDAAAAAAAAgIQeAAAAAAAA";
76 let signature = "BiVJlg3liA6MaHQ0Fw9kdmBbj+SuuaKGMseZXPO6gx2XYx0AAAAAhgF7InR5cGUiOiJ3ZWJhdXRobi5nZXQiLCJjaGFsbGVuZ2UiOiJXellBZmVvbHcweU15bEFheDRvbzNjVC1rdEVaM0xmenZXcURqakxKZVRvIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo1MTczIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfWICfOgpQ38QYao9Gj0/bqmWYNkuxvbuN3lz4uzFcXeVMEVivX41eC9H+tk+UnvUvKzThtf+uMLFzerU0zZLi8le4QJJsAUcyjsP/1UPAesax8UOC14M62FjAqtqaR46wR7jCg==";
77
78 let transaction: Transaction = {
79 use base64ct::Encoding;
80 let bytes = base64ct::Base64::decode_vec(transaction).unwrap();
81 bcs::from_bytes(&bytes).unwrap()
82 };
83 let signature = UserSignature::from_base64(signature).unwrap();
84
85 let verifier = PasskeyVerifier::default();
86 verifier
87 .verify_transaction(&transaction, &signature)
88 .unwrap();
89 }
90}