use crate::Config;
use frame_support::{
pallet_prelude::{ConstU32, Decode, Encode, MaxEncodedLen, TypeInfo},
BoundedVec, RuntimeDebugNoBound,
};
use p256::{ecdsa::signature::Verifier, EncodedPoint};
use sp_io::hashing::sha2_256;
use sp_runtime::MultiSignature;
#[allow(unused)]
use sp_std::boxed::Box;
use sp_std::vec::Vec;
pub const CHALLENGE_PLACEHOLDER: &str = "#rplc#";
pub type PasskeyAuthenticatorData = BoundedVec<u8, ConstU32<128>>;
pub type PasskeyClientDataJson = BoundedVec<u8, ConstU32<256>>;
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, RuntimeDebugNoBound, Clone)]
pub struct PasskeyPublicKey(pub [u8; 33]);
#[derive(
Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, RuntimeDebugNoBound, Clone, Default,
)]
pub struct PasskeySignature(pub BoundedVec<u8, ConstU32<96>>);
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, RuntimeDebugNoBound, Clone)]
#[scale_info(skip_type_params(T))]
pub struct PasskeyPayload<T: Config> {
pub passkey_public_key: PasskeyPublicKey,
pub verifiable_passkey_signature: VerifiablePasskeySignature,
pub passkey_call: PasskeyCall<T>,
}
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, RuntimeDebugNoBound, Clone)]
#[scale_info(skip_type_params(T))]
pub struct PasskeyPayloadV2<T: Config> {
pub passkey_public_key: PasskeyPublicKey,
pub verifiable_passkey_signature: VerifiablePasskeySignature,
pub account_ownership_proof: MultiSignature,
pub passkey_call: PasskeyCallV2<T>,
}
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, RuntimeDebugNoBound, Clone)]
pub struct VerifiablePasskeySignature {
pub signature: PasskeySignature,
pub authenticator_data: PasskeyAuthenticatorData,
pub client_data_json: PasskeyClientDataJson,
}
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, RuntimeDebugNoBound, Clone)]
#[scale_info(skip_type_params(T))]
pub struct PasskeyCall<T: Config> {
pub account_id: T::AccountId,
pub account_nonce: T::Nonce,
pub account_ownership_proof: MultiSignature,
pub call: Box<<T as Config>::RuntimeCall>,
}
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, RuntimeDebugNoBound, Clone)]
#[scale_info(skip_type_params(T))]
pub struct PasskeyCallV2<T: Config> {
pub account_id: T::AccountId,
pub account_nonce: T::Nonce,
pub call: Box<<T as Config>::RuntimeCall>,
}
impl<T: Config> From<PasskeyPayload<T>> for PasskeyPayloadV2<T> {
fn from(payload: PasskeyPayload<T>) -> Self {
PasskeyPayloadV2 {
passkey_public_key: payload.passkey_public_key,
verifiable_passkey_signature: payload.verifiable_passkey_signature,
account_ownership_proof: payload.passkey_call.account_ownership_proof,
passkey_call: PasskeyCallV2 {
account_id: payload.passkey_call.account_id,
account_nonce: payload.passkey_call.account_nonce,
call: payload.passkey_call.call,
},
}
}
}
impl<T: Config> From<PasskeyPayloadV2<T>> for PasskeyPayload<T> {
fn from(payload: PasskeyPayloadV2<T>) -> Self {
PasskeyPayload {
passkey_public_key: payload.passkey_public_key,
verifiable_passkey_signature: payload.verifiable_passkey_signature,
passkey_call: PasskeyCall {
account_id: payload.passkey_call.account_id,
account_ownership_proof: payload.account_ownership_proof,
account_nonce: payload.passkey_call.account_nonce,
call: payload.passkey_call.call,
},
}
}
}
impl PasskeySignature {
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
}
impl TryFrom<PasskeySignature> for p256::ecdsa::DerSignature {
type Error = ();
fn try_from(value: PasskeySignature) -> Result<Self, Self::Error> {
let result = p256::ecdsa::DerSignature::from_bytes(&value.to_vec()[..]).map_err(|_| ())?;
Ok(result)
}
}
impl PasskeyPublicKey {
pub fn inner(&self) -> [u8; 33] {
self.0
}
}
impl TryFrom<EncodedPoint> for PasskeyPublicKey {
type Error = ();
fn try_from(value: EncodedPoint) -> Result<Self, Self::Error> {
let bytes = value.as_bytes().to_vec();
let inner: [u8; 33] = bytes.try_into().map_err(|_| ())?;
Ok(PasskeyPublicKey(inner))
}
}
impl TryFrom<&PasskeyPublicKey> for p256::ecdsa::VerifyingKey {
type Error = ();
fn try_from(value: &PasskeyPublicKey) -> Result<Self, Self::Error> {
let encoded_point = EncodedPoint::from_bytes(&value.inner()[..]).map_err(|_| ())?;
let result =
p256::ecdsa::VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| ())?;
Ok(result)
}
}
impl TryFrom<Vec<u8>> for PasskeySignature {
type Error = ();
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let inner: BoundedVec<u8, ConstU32<96>> = value.try_into().map_err(|_| ())?;
Ok(PasskeySignature(inner))
}
}
pub enum PasskeyVerificationError {
InvalidSignature,
InvalidPublicKey,
InvalidClientDataJson,
InvalidProof,
InvalidAuthenticatorData,
}
impl From<PasskeyVerificationError> for u8 {
fn from(value: PasskeyVerificationError) -> Self {
match value {
PasskeyVerificationError::InvalidSignature => 0u8,
PasskeyVerificationError::InvalidPublicKey => 1u8,
PasskeyVerificationError::InvalidClientDataJson => 2u8,
PasskeyVerificationError::InvalidProof => 3u8,
PasskeyVerificationError::InvalidAuthenticatorData => 4u8,
}
}
}
impl VerifiablePasskeySignature {
pub fn try_verify(
&self,
msg: &[u8],
signer: &PasskeyPublicKey,
) -> Result<(), PasskeyVerificationError> {
let verifying_key: p256::ecdsa::VerifyingKey =
signer.try_into().map_err(|_| PasskeyVerificationError::InvalidPublicKey)?;
let passkey_signature: p256::ecdsa::DerSignature = self
.signature
.clone()
.try_into()
.map_err(|_| PasskeyVerificationError::InvalidSignature)?;
let calculated_challenge = sha2_256(msg);
let calculated_challenge_base64url = base64_url::encode(&calculated_challenge);
let str_of_json = core::str::from_utf8(&self.client_data_json)
.map_err(|_| PasskeyVerificationError::InvalidClientDataJson)?;
let original_client_data_json =
str_of_json.replace(CHALLENGE_PLACEHOLDER, &calculated_challenge_base64url);
let mut passkey_signature_payload = self.authenticator_data.to_vec();
if passkey_signature_payload.len() < 37 {
return Err(PasskeyVerificationError::InvalidAuthenticatorData);
}
passkey_signature_payload
.extend_from_slice(&sha2_256(&original_client_data_json.as_bytes()));
verifying_key
.verify(&passkey_signature_payload, &passkey_signature)
.map_err(|_| PasskeyVerificationError::InvalidProof)
}
}