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
16pub const CHALLENGE_PLACEHOLDER: &str = "#rplc#";
19pub type PasskeyAuthenticatorData = BoundedVec<u8, ConstU32<128>>;
22pub type PasskeyClientDataJson = BoundedVec<u8, ConstU32<256>>;
27#[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 static ref MAIN_TYPE_HASH: [u8; 32] =
46 sp_io::hashing::keccak_256(b"PasskeyPublicKey(bytes publicKey)");
47 }
48 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#[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#[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 pub passkey_public_key: PasskeyPublicKey,
87 pub verifiable_passkey_signature: VerifiablePasskeySignature,
89 pub passkey_call: PasskeyCall<T>,
91}
92
93#[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 pub passkey_public_key: PasskeyPublicKey,
108 pub verifiable_passkey_signature: VerifiablePasskeySignature,
110 pub account_ownership_proof: MultiSignature,
112 pub passkey_call: PasskeyCallV2<T>,
114}
115
116#[derive(
118 Encode,
119 Decode,
120 DecodeWithMemTracking,
121 TypeInfo,
122 MaxEncodedLen,
123 PartialEq,
124 RuntimeDebugNoBound,
125 Clone,
126)]
127pub struct VerifiablePasskeySignature {
128 pub signature: PasskeySignature,
130 pub authenticator_data: PasskeyAuthenticatorData,
132 pub client_data_json: PasskeyClientDataJson,
134}
135
136#[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 pub account_id: T::AccountId,
151 pub account_nonce: T::Nonce,
153 pub account_ownership_proof: MultiSignature,
155 pub call: Box<<T as Config>::RuntimeCall>,
157}
158
159#[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 pub account_id: T::AccountId,
174 pub account_nonce: T::Nonce,
176 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 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 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
265pub enum PasskeyVerificationError {
267 InvalidSignature,
269 InvalidPublicKey,
271 InvalidClientDataJson,
273 InvalidProof,
275 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 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 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 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 verifying_key
324 .verify(&passkey_signature_payload, &passkey_signature)
325 .map_err(|_| PasskeyVerificationError::InvalidProof)
326 }
327}