pallet_passkey/
types.rs

1use crate::Config;
2use frame_support::{
3	pallet_prelude::{ConstU32, Decode, Encode, MaxEncodedLen, TypeInfo},
4	BoundedVec, RuntimeDebugNoBound,
5};
6use p256::{ecdsa::signature::Verifier, EncodedPoint};
7use parity_scale_codec::DecodeWithMemTracking;
8use sp_io::hashing::sha2_256;
9use sp_runtime::MultiSignature;
10extern crate alloc;
11#[allow(unused)]
12use alloc::{boxed::Box, sync::Arc, vec::Vec};
13use common_primitives::{node::EIP712Encode, signatures::get_eip712_encoding_prefix};
14use lazy_static::lazy_static;
15
16/// This is the placeholder value that should be replaced by calculated challenge for
17/// evaluation of a Passkey signature.
18pub const CHALLENGE_PLACEHOLDER: &str = "#rplc#";
19/// Passkey AuthenticatorData type. The length is 37 bytes or more
20/// <https://w3c.github.io/webauthn/#authenticator-data>
21pub type PasskeyAuthenticatorData = BoundedVec<u8, ConstU32<128>>;
22/// Passkey ClientDataJson type
23/// Note: The `challenge` field inside this json MUST be replaced with `CHALLENGE_PLACEHOLDER`
24/// before submission to the chain
25/// <https://w3c.github.io/webauthn/#dictdef-collectedclientdata>
26pub type PasskeyClientDataJson = BoundedVec<u8, ConstU32<256>>;
27/// PassKey Public Key type in compressed encoded point format
28/// the first byte is the tag indicating compressed format
29#[derive(
30	Encode,
31	Decode,
32	DecodeWithMemTracking,
33	TypeInfo,
34	MaxEncodedLen,
35	PartialEq,
36	RuntimeDebugNoBound,
37	Clone,
38)]
39pub struct PasskeyPublicKey(pub [u8; 33]);
40
41impl EIP712Encode for PasskeyPublicKey {
42	fn encode_eip_712(&self, chain_id: u32) -> Box<[u8]> {
43		lazy_static! {
44			// signed payload
45			static ref MAIN_TYPE_HASH: [u8; 32] =
46				sp_io::hashing::keccak_256(b"PasskeyPublicKey(bytes publicKey)");
47		}
48		// get prefix and domain separator
49		let prefix_domain_separator: Box<[u8]> =
50			get_eip712_encoding_prefix("0xcccccccccccccccccccccccccccccccccccccccc", chain_id);
51		let coded_public_key = sp_io::hashing::keccak_256(self.0.as_slice());
52		let message =
53			sp_io::hashing::keccak_256(&[MAIN_TYPE_HASH.as_slice(), &coded_public_key].concat());
54		let combined = [prefix_domain_separator.as_ref(), &message].concat();
55		combined.into_boxed_slice()
56	}
57}
58/// PassKey Signature type
59#[derive(
60	Encode,
61	Decode,
62	DecodeWithMemTracking,
63	TypeInfo,
64	MaxEncodedLen,
65	PartialEq,
66	RuntimeDebugNoBound,
67	Clone,
68	Default,
69)]
70pub struct PasskeySignature(pub BoundedVec<u8, ConstU32<96>>);
71
72/// Passkey Payload
73#[derive(
74	Encode,
75	Decode,
76	DecodeWithMemTracking,
77	TypeInfo,
78	MaxEncodedLen,
79	PartialEq,
80	RuntimeDebugNoBound,
81	Clone,
82)]
83#[scale_info(skip_type_params(T))]
84pub struct PasskeyPayload<T: Config> {
85	/// passkey public key
86	pub passkey_public_key: PasskeyPublicKey,
87	/// a self-contained verifiable passkey signature with all required metadata
88	pub verifiable_passkey_signature: VerifiablePasskeySignature,
89	/// PassKey Call
90	pub passkey_call: PasskeyCall<T>,
91}
92
93/// Passkey Payload V2
94#[derive(
95	Encode,
96	Decode,
97	DecodeWithMemTracking,
98	TypeInfo,
99	MaxEncodedLen,
100	PartialEq,
101	RuntimeDebugNoBound,
102	Clone,
103)]
104#[scale_info(skip_type_params(T))]
105pub struct PasskeyPayloadV2<T: Config> {
106	/// passkey public key
107	pub passkey_public_key: PasskeyPublicKey,
108	/// a self-contained verifiable passkey signature with all required metadata
109	pub verifiable_passkey_signature: VerifiablePasskeySignature,
110	/// passkey_public_key signed by account_id's private key
111	pub account_ownership_proof: MultiSignature,
112	/// PassKey Call
113	pub passkey_call: PasskeyCallV2<T>,
114}
115
116/// A verifiable Pass key contains all the required information to verify a passkey signature
117#[derive(
118	Encode,
119	Decode,
120	DecodeWithMemTracking,
121	TypeInfo,
122	MaxEncodedLen,
123	PartialEq,
124	RuntimeDebugNoBound,
125	Clone,
126)]
127pub struct VerifiablePasskeySignature {
128	/// passkey signature of `passkey_call`
129	pub signature: PasskeySignature,
130	/// passkey authenticator data
131	pub authenticator_data: PasskeyAuthenticatorData,
132	/// passkey client data in json format
133	pub client_data_json: PasskeyClientDataJson,
134}
135
136/// Inner Passkey call
137#[derive(
138	Encode,
139	Decode,
140	DecodeWithMemTracking,
141	TypeInfo,
142	MaxEncodedLen,
143	PartialEq,
144	RuntimeDebugNoBound,
145	Clone,
146)]
147#[scale_info(skip_type_params(T))]
148pub struct PasskeyCall<T: Config> {
149	/// account id which is the origin of this call
150	pub account_id: T::AccountId,
151	/// account nonce
152	pub account_nonce: T::Nonce,
153	/// passkey_public_key signed by account_id's private key
154	pub account_ownership_proof: MultiSignature,
155	/// Extrinsic call
156	pub call: Box<<T as Config>::RuntimeCall>,
157}
158
159/// Inner Passkey call V2
160#[derive(
161	Encode,
162	Decode,
163	DecodeWithMemTracking,
164	TypeInfo,
165	MaxEncodedLen,
166	PartialEq,
167	RuntimeDebugNoBound,
168	Clone,
169)]
170#[scale_info(skip_type_params(T))]
171pub struct PasskeyCallV2<T: Config> {
172	/// account id which is the origin of this call
173	pub account_id: T::AccountId,
174	/// account nonce
175	pub account_nonce: T::Nonce,
176	/// Extrinsic call
177	pub call: Box<<T as Config>::RuntimeCall>,
178}
179
180impl<T: Config> From<PasskeyPayload<T>> for PasskeyPayloadV2<T> {
181	fn from(payload: PasskeyPayload<T>) -> Self {
182		PasskeyPayloadV2 {
183			passkey_public_key: payload.passkey_public_key,
184			verifiable_passkey_signature: payload.verifiable_passkey_signature,
185			account_ownership_proof: payload.passkey_call.account_ownership_proof,
186			passkey_call: PasskeyCallV2 {
187				account_id: payload.passkey_call.account_id,
188				account_nonce: payload.passkey_call.account_nonce,
189				call: payload.passkey_call.call,
190			},
191		}
192	}
193}
194
195impl<T: Config> From<PasskeyPayloadV2<T>> for PasskeyPayload<T> {
196	fn from(payload: PasskeyPayloadV2<T>) -> Self {
197		PasskeyPayload {
198			passkey_public_key: payload.passkey_public_key,
199			verifiable_passkey_signature: payload.verifiable_passkey_signature,
200			passkey_call: PasskeyCall {
201				account_id: payload.passkey_call.account_id,
202				account_ownership_proof: payload.account_ownership_proof,
203				account_nonce: payload.passkey_call.account_nonce,
204				call: payload.passkey_call.call,
205			},
206		}
207	}
208}
209
210impl PasskeySignature {
211	/// returns the inner raw data as a vector
212	pub fn to_vec(&self) -> Vec<u8> {
213		self.0.to_vec()
214	}
215}
216
217impl TryFrom<PasskeySignature> for p256::ecdsa::DerSignature {
218	type Error = ();
219
220	fn try_from(value: PasskeySignature) -> Result<Self, Self::Error> {
221		let result = p256::ecdsa::DerSignature::from_bytes(&value.to_vec()[..]).map_err(|_| ())?;
222		Ok(result)
223	}
224}
225
226impl PasskeyPublicKey {
227	/// returns the inner raw data
228	pub fn inner(&self) -> [u8; 33] {
229		self.0
230	}
231}
232
233impl TryFrom<EncodedPoint> for PasskeyPublicKey {
234	type Error = ();
235
236	fn try_from(value: EncodedPoint) -> Result<Self, Self::Error> {
237		let bytes = value.as_bytes().to_vec();
238		let inner: [u8; 33] = bytes.try_into().map_err(|_| ())?;
239		Ok(PasskeyPublicKey(inner))
240	}
241}
242
243impl TryFrom<&PasskeyPublicKey> for p256::ecdsa::VerifyingKey {
244	type Error = ();
245
246	fn try_from(value: &PasskeyPublicKey) -> Result<Self, Self::Error> {
247		let encoded_point = EncodedPoint::from_bytes(&value.inner()[..]).map_err(|_| ())?;
248
249		let result =
250			p256::ecdsa::VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| ())?;
251
252		Ok(result)
253	}
254}
255
256impl TryFrom<Vec<u8>> for PasskeySignature {
257	type Error = ();
258
259	fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
260		let inner: BoundedVec<u8, ConstU32<96>> = value.try_into().map_err(|_| ())?;
261		Ok(PasskeySignature(inner))
262	}
263}
264
265/// Passkey verification error types
266pub enum PasskeyVerificationError {
267	/// Invalid Passkey signature
268	InvalidSignature,
269	/// Invalid Passkey public key
270	InvalidPublicKey,
271	/// Invalid Client data json
272	InvalidClientDataJson,
273	/// Invalid proof
274	InvalidProof,
275	/// Invalid authenticator data
276	InvalidAuthenticatorData,
277}
278
279impl From<PasskeyVerificationError> for u8 {
280	fn from(value: PasskeyVerificationError) -> Self {
281		match value {
282			PasskeyVerificationError::InvalidSignature => 0u8,
283			PasskeyVerificationError::InvalidPublicKey => 1u8,
284			PasskeyVerificationError::InvalidClientDataJson => 2u8,
285			PasskeyVerificationError::InvalidProof => 3u8,
286			PasskeyVerificationError::InvalidAuthenticatorData => 4u8,
287		}
288	}
289}
290
291impl VerifiablePasskeySignature {
292	/// verifying a P256 Passkey signature
293	pub fn try_verify(
294		&self,
295		msg: &[u8],
296		signer: &PasskeyPublicKey,
297	) -> Result<(), PasskeyVerificationError> {
298		let verifying_key: p256::ecdsa::VerifyingKey =
299			signer.try_into().map_err(|_| PasskeyVerificationError::InvalidPublicKey)?;
300		let passkey_signature: p256::ecdsa::DerSignature = self
301			.signature
302			.clone()
303			.try_into()
304			.map_err(|_| PasskeyVerificationError::InvalidSignature)?;
305		let calculated_challenge = sha2_256(msg);
306		let calculated_challenge_base64url = base64_url::encode(&calculated_challenge);
307
308		// inject challenge inside clientJsonData
309		let str_of_json = core::str::from_utf8(&self.client_data_json)
310			.map_err(|_| PasskeyVerificationError::InvalidClientDataJson)?;
311		let original_client_data_json =
312			str_of_json.replace(CHALLENGE_PLACEHOLDER, &calculated_challenge_base64url);
313
314		// prepare signing payload which is [authenticator || sha256(client_data_json)]
315		let mut passkey_signature_payload = self.authenticator_data.to_vec();
316		if passkey_signature_payload.len() < 37 {
317			return Err(PasskeyVerificationError::InvalidAuthenticatorData);
318		}
319		passkey_signature_payload
320			.extend_from_slice(&sha2_256(original_client_data_json.as_bytes()));
321
322		// finally verify the passkey signature against the payload
323		verifying_key
324			.verify(&passkey_signature_payload, &passkey_signature)
325			.map_err(|_| PasskeyVerificationError::InvalidProof)
326	}
327}