sui_crypto/
passkey.rs

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        // Construct passkey signing message = authenticator_data || sha256(client_data_json).
38        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}