Skip to content

Commit

Permalink
sui-crypto: add support for verifying passkey authenticators
Browse files Browse the repository at this point in the history
  • Loading branch information
bmwill committed Dec 19, 2024
1 parent 975b425 commit 1ba4c09
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 2 deletions.
4 changes: 4 additions & 0 deletions crates/sui-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ rustdoc-args = [
default = []
ed25519 = ["dep:ed25519-dalek", "dep:rand_core"]
secp256r1 = ["dep:p256", "dep:rand_core"]
passkey = ["secp256r1", "dep:sha2"]
secp256k1 = ["dep:k256", "dep:rand_core", "signature/std"]
zklogin = [
"dep:ark-bn254",
Expand Down Expand Up @@ -64,6 +65,9 @@ ed25519-dalek = { version = "2.1.1", optional = true }
# secp256r1 support
p256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "std"], optional = true }

# passkey verification support
sha2 = { version = "0.10.8", optional = true }

# secp256k1 support
k256 = { version = "0.13.4", default-features = false, features = ["ecdsa"], optional = true }

Expand Down
4 changes: 4 additions & 0 deletions crates/sui-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ pub mod secp256k1;
#[cfg_attr(doc_cfg, doc(cfg(feature = "secp256r1")))]
pub mod secp256r1;

#[cfg(feature = "passkey")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "passkey")))]
pub mod passkey;

#[cfg(feature = "zklogin")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "zklogin")))]
pub mod zklogin;
Expand Down
8 changes: 6 additions & 2 deletions crates/sui-crypto/src/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,14 @@ impl Verifier<UserSignature> for UserSignatureVerifier {

zklogin_verifier.verify(message, zklogin_authenticator.as_ref())
}

#[cfg(not(feature = "passkey"))]
UserSignature::Passkey(_) => Err(SignatureError::from_source(
"unsupported user signature scheme",
"support for passkey is not enabled",
)),
#[cfg(feature = "passkey")]
UserSignature::Passkey(passkey_authenticator) => {
crate::passkey::PasskeyVerifier::default().verify(message, passkey_authenticator)
}
}
}
}
Expand Down
90 changes: 90 additions & 0 deletions crates/sui-crypto/src/passkey.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::secp256r1::Secp256r1VerifyingKey;
use crate::SignatureError;
use signature::Verifier;
use sui_sdk_types::types::PasskeyAuthenticator;
use sui_sdk_types::types::SimpleSignature;
use sui_sdk_types::types::UserSignature;

#[derive(Default, Clone, Debug)]
pub struct PasskeyVerifier {}

impl PasskeyVerifier {
pub fn new() -> Self {
Self {}
}
}

impl Verifier<PasskeyAuthenticator> for PasskeyVerifier {
fn verify(
&self,
message: &[u8],
authenticator: &PasskeyAuthenticator,
) -> Result<(), SignatureError> {
let SimpleSignature::Secp256r1 {
signature,
public_key,
} = authenticator.signature()
else {
return Err(SignatureError::from_source("not a secp256r1 signature"));
};

if message != authenticator.challenge() {
return Err(SignatureError::from_source(
"passkey challenge does not match expected message",
));
}

// Construct passkey signing message = authenticator_data || sha256(client_data_json).
let mut message = authenticator.authenticator_data().to_owned();
let client_data_hash = {
use sha2::Digest;

let mut hasher = sha2::Sha256::new();
hasher.update(authenticator.client_data_json().as_bytes());
hasher.finalize()
};
message.extend_from_slice(&client_data_hash);

let verifying_key = Secp256r1VerifyingKey::new(&public_key)?;

verifying_key.verify(&message, &signature)
}
}

impl Verifier<UserSignature> for PasskeyVerifier {
fn verify(&self, message: &[u8], signature: &UserSignature) -> Result<(), SignatureError> {
let UserSignature::Passkey(authenticator) = signature else {
return Err(SignatureError::from_source("not a passkey authenticator"));
};

<Self as Verifier<PasskeyAuthenticator>>::verify(self, message, authenticator)
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::SuiVerifier;
use sui_sdk_types::types::Transaction;

#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;

#[test]
fn transaction_signing_fixture() {
let transaction = "AAAAACdZawPnpJRjmVcwDu6xrIumtq5NLO+6GHbs0iGdCoD7AQ0T0TolicYERdSvyCRjSSduDZLbSpBsZBoib+lF48EBcgAAAAAAAAAgpQr/Mudl9BdzyBdkbqTlqBw4/aJ21kAD/jpJKa05im4nWWsD56SUY5lXMA7usayLprauTSzvuhh27NIhnQqA++gDAAAAAAAAgIQeAAAAAAAA";
let signature = "BiVJlg3liA6MaHQ0Fw9kdmBbj+SuuaKGMseZXPO6gx2XYx0AAAAAhgF7InR5cGUiOiJ3ZWJhdXRobi5nZXQiLCJjaGFsbGVuZ2UiOiJXellBZmVvbHcweU15bEFheDRvbzNjVC1rdEVaM0xmenZXcURqakxKZVRvIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo1MTczIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfWICfOgpQ38QYao9Gj0/bqmWYNkuxvbuN3lz4uzFcXeVMEVivX41eC9H+tk+UnvUvKzThtf+uMLFzerU0zZLi8le4QJJsAUcyjsP/1UPAesax8UOC14M62FjAqtqaR46wR7jCg==";

let transaction: Transaction = {
use base64ct::Encoding;
let bytes = base64ct::Base64::decode_vec(transaction).unwrap();
bcs::from_bytes(&bytes).unwrap()
};
let signature = UserSignature::from_base64(signature).unwrap();

let verifier = PasskeyVerifier::default();
verifier
.verify_transaction(&transaction, &signature)
.unwrap();
}
}
4 changes: 4 additions & 0 deletions crates/sui-sdk-types/src/types/crypto/passkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ impl PasskeyAuthenticator {
&self.client_data_json
}

pub fn challenge(&self) -> &[u8] {
&self.challenge
}

pub fn signature(&self) -> SimpleSignature {
SimpleSignature::Secp256r1 {
signature: self.signature,
Expand Down

0 comments on commit 1ba4c09

Please sign in to comment.