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