Skip to content

Commit d8faae7

Browse files
authored
passkey: reorder validation and some docs (#2071)
# Goal The goal of this PR is to re order the validations to be as close as possible to the signed extension order considering the customizations that we did such as not having post_dispatch implemented. Related to #2032
1 parent d7f2d31 commit d8faae7

File tree

3 files changed

+82
-52
lines changed

3 files changed

+82
-52
lines changed

pallets/passkey/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
11
# Passkey Pallet
22

33
Provides a way to execute transactions using passkey signatures.
4+
5+
## Summary
6+
7+
Passkeys are a secure alternative to passwords for authentication. Due to its ease of use for the
8+
users and having a well established support in different platforms, Frequency chain supports passkey
9+
p256 signatures for select transactions. With this feature users would be able to execute a
10+
transactions by authenticating themselves with a Passkey P256 signature.
11+
12+
### Actions
13+
14+
The Passkey pallet provides for:
15+
16+
- Executing supported transactions with a valid Passkey P256 signature
17+
18+
## Interactions
19+
20+
### Extrinsic verification
21+
Because the Polkadot SDK currently lacks support for P256 signatures, we had to use an unsigned
22+
extrinsic to allow this custom verification before dispatching transactions. To achieve this, we
23+
added P256 signature verification within the `ValidateUnsigned` trait implementation for the pallet.
24+
25+
Since **unsigned extrinsics** bypass verification by `SignedExtensions`, we've added the necessary
26+
checks within the ValidateUnsigned trait implementation to mitigate potential vulnerabilities.
27+
28+
### Extrinsics
29+
30+
| Name/Description | Caller | Payment | Key Events | Runtime Added |
31+
|----------------------------------------|--------| ------------------ |-------------------------------------------------------------------------------------------------------------------------------------------|---------------|
32+
| `proxy`<br />Proxies an extrinsic call | Anyone | Tokens | [`TransactionExecutionSuccess`](https://frequency-chain.github.io/frequency/pallet_passkey/module/enum.Event.html#variant.TransactionExecutionSuccess) | 92 |
33+
34+
See [Rust Docs](https://frequency-chain.github.io/frequency/pallet_passkey/module/struct.Pallet.html) for more details.
35+
36+

pallets/passkey/src/lib.rs

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub mod module {
9696
/// Filters the inner calls for passkey which is set in runtime
9797
type PasskeyCallFilter: Contains<<Self as Config>::RuntimeCall>;
9898

99-
/// Helper Curreny method for benchmarking
99+
/// Helper Currency method for benchmarking
100100
#[cfg(feature = "runtime-benchmarks")]
101101
type Currency: Mutate<Self::AccountId>;
102102
}
@@ -123,7 +123,9 @@ pub mod module {
123123

124124
#[pallet::call]
125125
impl<T: Config> Pallet<T> {
126-
/// proxy call
126+
/// Proxies an extrinsic call by changing the origin to `account_id` inside the payload.
127+
/// Since this is an unsigned extrinsic all the verification checks are performed inside
128+
/// `validate_unsigned` and `pre_dispatch` hooks.
127129
#[pallet::call_index(0)]
128130
#[pallet::weight({
129131
let dispatch_info = payload.passkey_call.call.get_dispatch_info();
@@ -159,59 +161,51 @@ pub mod module {
159161
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
160162
{
161163
type Call = Call<T>;
164+
165+
/// Validating the regular checks of an extrinsic plus verifying the P256 Passkey signature
166+
/// The majority of these checks are the same as `SignedExtra` list in defined in runtime
162167
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
163168
let valid_tx = ValidTransaction::default();
164169
let payload = Self::filter_valid_calls(&call)?;
165170

166-
let frame_system_checks =
167-
FrameSystemChecks(payload.passkey_call.account_id.clone(), call.clone());
168-
let frame_system_validity = frame_system_checks.validate()?;
169-
170-
let signatures_check = PasskeySignatureCheck::new(payload.clone());
171-
let signature_validity = signatures_check.validate()?;
172-
173-
let nonce_check = PasskeyNonceCheck::new(payload.passkey_call.clone());
174-
let nonce_validity = nonce_check.validate()?;
175-
176-
let tx_charge = ChargeTransactionPayment::<T>(
171+
let frame_system_validity =
172+
FrameSystemChecks(payload.passkey_call.account_id.clone(), call.clone())
173+
.validate()?;
174+
let nonce_validity = PasskeyNonceCheck::new(payload.passkey_call.clone()).validate()?;
175+
let weight_validity = PasskeyWeightCheck::new(call.clone()).validate()?;
176+
// this is the last (except for the fee validation check) since it is the heaviest
177+
let signature_validity = PasskeySignatureCheck::new(payload.clone()).validate()?;
178+
// this should be last since we are not refunding in the case of failure since we didn't
179+
// have `post_dispatch` implemented this should be the last check executed
180+
let tx_payment_validity = ChargeTransactionPayment::<T>(
177181
payload.passkey_call.account_id.clone(),
178182
call.clone(),
179-
);
180-
let tx_payment_validity = tx_charge.validate()?;
181-
182-
let weight_check = PasskeyWeightCheck::new(call.clone());
183-
let weight_validity = weight_check.validate()?;
183+
)
184+
.validate()?;
184185

185186
let valid_tx = valid_tx
186187
.combine_with(frame_system_validity)
187-
.combine_with(signature_validity)
188188
.combine_with(nonce_validity)
189-
.combine_with(tx_payment_validity)
190-
.combine_with(weight_validity);
189+
.combine_with(weight_validity)
190+
.combine_with(signature_validity)
191+
.combine_with(tx_payment_validity);
191192
Ok(valid_tx)
192193
}
193194

195+
/// Checking and executing a list of operations pre_dispatch
196+
/// The majority of these checks are the same as `SignedExtra` list in defined in runtime
194197
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
195198
let payload = Self::filter_valid_calls(&call)?;
196-
197-
let frame_system_checks =
198-
FrameSystemChecks(payload.passkey_call.account_id.clone(), call.clone());
199-
frame_system_checks.pre_dispatch()?;
200-
201-
let signatures_check = PasskeySignatureCheck::new(payload.clone());
202-
signatures_check.pre_dispatch()?;
203-
204-
let nonce_check = PasskeyNonceCheck::new(payload.passkey_call.clone());
205-
nonce_check.pre_dispatch()?;
206-
207-
let tx_charge = ChargeTransactionPayment::<T>(
208-
payload.passkey_call.account_id.clone(),
209-
call.clone(),
210-
);
211-
tx_charge.pre_dispatch()?;
212-
213-
let weight_check = PasskeyWeightCheck::new(call.clone());
214-
weight_check.pre_dispatch()
199+
FrameSystemChecks(payload.passkey_call.account_id.clone(), call.clone())
200+
.pre_dispatch()?;
201+
PasskeyNonceCheck::new(payload.passkey_call.clone()).pre_dispatch()?;
202+
PasskeyWeightCheck::new(call.clone()).pre_dispatch()?;
203+
// this is the last (except for the fee validation check) since it is the heaviest
204+
PasskeySignatureCheck::new(payload.clone()).pre_dispatch()?;
205+
// this should be last since we are not refunding in the case of failure since we didn't
206+
// have `post_dispatch` implemented this should be the last check executed
207+
ChargeTransactionPayment::<T>(payload.passkey_call.account_id.clone(), call.clone())
208+
.pre_dispatch()
215209
}
216210
}
217211
}
@@ -222,6 +216,7 @@ where
222216
<T as frame_system::Config>::RuntimeCall:
223217
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
224218
{
219+
/// Filtering the valid calls and extracting the payload from inside the call
225220
fn filter_valid_calls(call: &Call<T>) -> Result<PasskeyPayload<T>, TransactionValidityError> {
226221
match call {
227222
Call::proxy { payload }
@@ -232,7 +227,7 @@ where
232227
}
233228
}
234229

235-
/// Passkey specific nonce check
230+
/// Passkey specific nonce check which is a wrapper around `CheckNonce` extension
236231
#[derive(Encode, Decode, Clone, TypeInfo)]
237232
#[scale_info(skip_type_params(T))]
238233
struct PasskeyNonceCheck<T: Config>(pub PasskeyCall<T>);
@@ -266,7 +261,9 @@ where
266261
}
267262
}
268263

269-
/// Passkey signatures check
264+
/// Passkey signatures check which verifies 2 signatures
265+
/// 1. Account signature of the P256 public key
266+
/// 2. Passkey P256 signature of the account public key
270267
#[derive(Encode, Decode, Clone, TypeInfo)]
271268
#[scale_info(skip_type_params(T))]
272269
struct PasskeySignatureCheck<T: Config>(pub PasskeyPayload<T>);
@@ -277,6 +274,14 @@ impl<T: Config> PasskeySignatureCheck<T> {
277274
}
278275

279276
pub fn validate(&self) -> TransactionValidity {
277+
// checking account signature to verify ownership of the account used
278+
let signed_data = self.0.passkey_public_key.clone();
279+
let signature = self.0.passkey_call.account_ownership_proof.clone();
280+
let signer = &self.0.passkey_call.account_id;
281+
282+
Self::check_account_signature(signer, &signed_data.inner().to_vec(), &signature)
283+
.map_err(|_e| TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?;
284+
280285
// checking the passkey signature to ensure access to the passkey
281286
let p256_signed_data = self.0.passkey_call.encode();
282287
let p256_signature = self.0.verifiable_passkey_signature.clone();
@@ -290,15 +295,7 @@ impl<T: Config> PasskeySignatureCheck<T> {
290295
_ => TransactionValidityError::Invalid(InvalidTransaction::Custom(e.into())),
291296
})?;
292297

293-
// checking account signature to verify ownership of the account used
294-
let signed_data = self.0.passkey_public_key.clone();
295-
let signature = self.0.passkey_call.account_ownership_proof.clone();
296-
let signer = &self.0.passkey_call.account_id;
297-
298-
match Self::check_account_signature(signer, &signed_data.inner().to_vec(), &signature) {
299-
Ok(_) => Ok(ValidTransaction::default()),
300-
Err(_e) => InvalidTransaction::BadSigner.into(),
301-
}
298+
Ok(ValidTransaction::default())
302299
}
303300

304301
pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {

pallets/passkey/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use sp_std::vec::Vec;
1414
/// evaluation of a Passkey signature.
1515
pub const CHALLENGE_PLACEHOLDER: &str = "#rplc#";
1616
/// Passkey AuthenticatorData type. The length is 37 bytes or more
17-
/// https://w3c.github.io/webauthn/#authenticator-data
17+
/// <https://w3c.github.io/webauthn/#authenticator-data>
1818
pub type PasskeyAuthenticatorData = BoundedVec<u8, ConstU32<128>>;
1919
/// Passkey ClientDataJson type
2020
/// Note: The `challenge` field inside this json MUST be replaced with `CHALLENGE_PLACEHOLDER`

0 commit comments

Comments
 (0)