Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Passkey Proxy v2, Simple Param Shift #2242

Merged
merged 4 commits into from
Dec 16, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add Passkey ProxyV2
wilwade committed Dec 13, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 2bfd976d4df4b1425a5c34456f62ef69a66db2ad
12 changes: 6 additions & 6 deletions pallets/passkey/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ mod app_sr25519 {

type SignerId = app_sr25519::Public;

fn generate_payload<T: Config>() -> PasskeyPayload<T> {
fn generate_payload<T: Config>() -> PasskeyPayloadV2<T> {
let test_account_1_pk = SignerId::generate_pair(None);
let test_account_1_account_id =
T::AccountId::decode(&mut &test_account_1_pk.encode()[..]).unwrap();
@@ -47,22 +47,22 @@ fn generate_payload<T: Config>() -> PasskeyPayload<T> {
let inner_call: <T as Config>::RuntimeCall =
frame_system::Call::<T>::remark { remark: vec![] }.into();

let call: PasskeyCall<T> = PasskeyCall {
let call: PasskeyCallV2<T> = PasskeyCallV2 {
account_id: test_account_1_account_id,
account_nonce: T::Nonce::zero(),
account_ownership_proof: signature,
call: Box::new(inner_call),
};

let passkey_signature =
passkey_sign(&secret, &call.encode(), &client_data, &authenticator).unwrap();
let payload = PasskeyPayload {
let payload = PasskeyPayloadV2 {
passkey_public_key,
verifiable_passkey_signature: VerifiablePasskeySignature {
signature: passkey_signature,
client_data_json: client_data.try_into().unwrap(),
authenticator_data: authenticator.try_into().unwrap(),
},
account_ownership_proof: signature,
passkey_call: call,
};
payload
@@ -77,13 +77,13 @@ benchmarks! {
validate {
let payload = generate_payload::<T>();
}: {
assert_ok!(Passkey::validate_unsigned(TransactionSource::InBlock, &Call::proxy { payload }));
assert_ok!(Passkey::validate_unsigned(TransactionSource::InBlock, &Call::proxy_v2 { payload }));
}

pre_dispatch {
let payload = generate_payload::<T>();
}: {
assert_ok!(Passkey::pre_dispatch(&Call::proxy { payload }));
assert_ok!(Passkey::pre_dispatch(&Call::proxy_v2 { payload }));
}

impl_benchmark_test_suite!(
171 changes: 122 additions & 49 deletions pallets/passkey/src/lib.rs
Original file line number Diff line number Diff line change
@@ -46,6 +46,8 @@ mod test_common;
mod mock;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_v2;

pub mod weights;
pub use weights::*;
@@ -125,16 +127,35 @@ pub mod module {
/// Proxies an extrinsic call by changing the origin to `account_id` inside the payload.
/// Since this is an unsigned extrinsic all the verification checks are performed inside
/// `validate_unsigned` and `pre_dispatch` hooks.
#[deprecated(since = "1.15.2", note = "Use proxy_v2 instead")]
#[pallet::call_index(0)]
#[pallet::weight({
let dispatch_info = payload.passkey_call.call.get_dispatch_info();
let overhead = T::WeightInfo::pre_dispatch();
let total = overhead.saturating_add(dispatch_info.weight);
(total, dispatch_info.class)
})]
#[allow(deprecated)]
pub fn proxy(
origin: OriginFor<T>,
payload: PasskeyPayload<T>,
) -> DispatchResultWithPostInfo {
Self::proxy_v2(origin, payload.into())
}

/// Proxies an extrinsic call by changing the origin to `account_id` inside the payload.
/// Since this is an unsigned extrinsic all the verification checks are performed inside
/// `validate_unsigned` and `pre_dispatch` hooks.
#[pallet::call_index(1)]
#[pallet::weight({
let dispatch_info = payload.passkey_call.call.get_dispatch_info();
let overhead = T::WeightInfo::pre_dispatch();
let total = overhead.saturating_add(dispatch_info.weight);
(total, dispatch_info.class)
})]
pub fn proxy_v2(
origin: OriginFor<T>,
payload: PasskeyPayloadV2<T>,
) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
let transaction_account_id = payload.passkey_call.account_id.clone();
@@ -178,7 +199,14 @@ pub mod module {
)
.validate()?;
// this is the last since it is the heaviest
let signature_validity = PasskeySignatureCheck::new(payload.clone()).validate()?;
// Requires different calls for v1 vs v2
let signature_validity = match call {
Call::proxy { payload } =>
PasskeySignatureCheck::new(payload.clone()).validate()?,
Call::proxy_v2 { payload } =>
PasskeySignatureCheckV2::new(payload.clone()).validate()?,
_ => return Err(InvalidTransaction::Call.into()),
};

let valid_tx = valid_tx
.combine_with(frame_system_validity)
@@ -200,7 +228,14 @@ pub mod module {
ChargeTransactionPayment::<T>(payload.passkey_call.account_id.clone(), call.clone())
.pre_dispatch()?;
// this is the last since it is the heaviest
PasskeySignatureCheck::new(payload.clone()).pre_dispatch()
// Requires different calls for v1 vs v2
match call {
Call::proxy { payload } =>
PasskeySignatureCheck::new(payload.clone()).pre_dispatch(),
Call::proxy_v2 { payload } =>
PasskeySignatureCheckV2::new(payload.clone()).pre_dispatch(),
_ => return Err(InvalidTransaction::Call.into()),
}
}
}
}
@@ -211,10 +246,13 @@ where
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
/// Filtering the valid calls and extracting the payload from inside the call
fn filter_valid_calls(call: &Call<T>) -> Result<PasskeyPayload<T>, TransactionValidityError> {
/// Filtering the valid calls and extracting the Payload V2 from inside the call
fn filter_valid_calls(call: &Call<T>) -> Result<PasskeyPayloadV2<T>, TransactionValidityError> {
match call {
Call::proxy { payload }
if T::PasskeyCallFilter::contains(&payload.clone().passkey_call.call) =>
return Ok(payload.clone().into()),
Call::proxy_v2 { payload }
if T::PasskeyCallFilter::contains(&payload.clone().passkey_call.call) =>
return Ok(payload.clone()),
_ => return Err(InvalidTransaction::Call.into()),
@@ -225,13 +263,13 @@ where
/// Passkey specific nonce check which is a wrapper around `CheckNonce` extension
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
struct PasskeyNonceCheck<T: Config>(pub PasskeyCall<T>);
struct PasskeyNonceCheck<T: Config>(pub PasskeyCallV2<T>);

impl<T: Config> PasskeyNonceCheck<T>
where
<T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo>,
{
pub fn new(passkey_call: PasskeyCall<T>) -> Self {
pub fn new(passkey_call: PasskeyCallV2<T>) -> Self {
Self(passkey_call)
}

@@ -263,62 +301,97 @@ where
#[scale_info(skip_type_params(T))]
struct PasskeySignatureCheck<T: Config>(pub PasskeyPayload<T>);

/// Passkey V2 signatures check which verifies 2 signatures
/// 1. Account signature of the P256 public key
/// 2. Passkey P256 signature of the account public key
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
struct PasskeySignatureCheckV2<T: Config>(pub PasskeyPayloadV2<T>);

/// Check the signature on passkey public key by the account id
/// Returns Ok(()) if the signature is valid
/// Returns Err(InvalidAccountSignature) if the signature is invalid
/// # Arguments
/// * `signer` - The account id of the signer
/// * `signed_data` - The signed data
/// * `signature` - The signature
/// # Return
/// * `Ok(())` if the signature is valid
/// * `Err(InvalidAccountSignature)` if the signature is invalid
fn check_account_signature<T: Config>(
signer: &T::AccountId,
signed_data: &Vec<u8>,
signature: &MultiSignature,
) -> DispatchResult {
let key = T::ConvertIntoAccountId32::convert((*signer).clone());

if !check_signature(signature, key, signed_data.clone()) {
return Err(Error::<T>::InvalidAccountSignature.into());
}

Ok(())
}

/// Validation logic for the two signatures for a passkey call
/// Returns Ok(()) if the signatures are valid
fn validate_payload_pieces<T: Config>(
account_ownership_proof: &MultiSignature,
signer: &T::AccountId,
p256_signer: &PasskeyPublicKey,
p256_signature: &VerifiablePasskeySignature,
p256_signed_data: &Vec<u8>,
) -> TransactionValidity {
// checking account signature to verify ownership of the account used
check_account_signature::<T>(signer, &p256_signer.inner().to_vec(), &account_ownership_proof)
.map_err(|_e| TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?;
p256_signature
.try_verify(&p256_signed_data, &p256_signer)
.map_err(|e| match e {
PasskeyVerificationError::InvalidProof =>
TransactionValidityError::Invalid(InvalidTransaction::BadSigner),
_ => TransactionValidityError::Invalid(InvalidTransaction::Custom(e.into())),
})?;

Ok(ValidTransaction::default())
}

impl<T: Config> PasskeySignatureCheck<T> {
pub fn new(passkey_payload: PasskeyPayload<T>) -> Self {
Self(passkey_payload)
}

pub fn validate(&self) -> TransactionValidity {
// checking account signature to verify ownership of the account used
let signed_data = self.0.passkey_public_key.clone();
let signature = self.0.passkey_call.account_ownership_proof.clone();
let signer = &self.0.passkey_call.account_id;

Self::check_account_signature(signer, &signed_data.inner().to_vec(), &signature)
.map_err(|_e| TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?;

// checking the passkey signature to ensure access to the passkey
let p256_signed_data = self.0.passkey_call.encode();
let p256_signature = self.0.verifiable_passkey_signature.clone();
let p256_signer = self.0.passkey_public_key.clone();

p256_signature
.try_verify(&p256_signed_data, &p256_signer)
.map_err(|e| match e {
PasskeyVerificationError::InvalidProof =>
TransactionValidityError::Invalid(InvalidTransaction::BadSigner),
_ => TransactionValidityError::Invalid(InvalidTransaction::Custom(e.into())),
})?;

Ok(ValidTransaction::default())
validate_payload_pieces::<T>(
&self.0.passkey_call.account_ownership_proof,
&self.0.passkey_call.account_id,
&self.0.passkey_public_key,
&self.0.verifiable_passkey_signature,
&self.0.passkey_call.encode(),
)
}

pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let _ = self.validate()?;
Ok(())
}
}

/// Check the signature on passkey public key by the account id
/// Returns Ok(()) if the signature is valid
/// Returns Err(InvalidAccountSignature) if the signature is invalid
/// # Arguments
/// * `signer` - The account id of the signer
/// * `signed_data` - The signed data
/// * `signature` - The signature
/// # Return
/// * `Ok(())` if the signature is valid
/// * `Err(InvalidAccountSignature)` if the signature is invalid
fn check_account_signature(
signer: &T::AccountId,
signed_data: &Vec<u8>,
signature: &MultiSignature,
) -> DispatchResult {
let key = T::ConvertIntoAccountId32::convert((*signer).clone());

if !check_signature(signature, key, signed_data.clone()) {
return Err(Error::<T>::InvalidAccountSignature.into());
}
impl<T: Config> PasskeySignatureCheckV2<T> {
pub fn new(passkey_payload: PasskeyPayloadV2<T>) -> Self {
Self(passkey_payload)
}

pub fn validate(&self) -> TransactionValidity {
validate_payload_pieces::<T>(
&self.0.account_ownership_proof,
&self.0.passkey_call.account_id,
&self.0.passkey_public_key,
&self.0.verifiable_passkey_signature,
&self.0.passkey_call.encode(),
)
}

pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let _ = self.validate()?;
Ok(())
}
}
Loading