1use defuse_crypto::{Curve, Ed25519, P256, PublicKey, serde::AsCurve};
2use defuse_serde_utils::base64::{Base64, Unpadded, UrlSafe};
3use near_sdk::{env, near, serde_json};
4use serde_with::serde_as;
5
6#[near(serializers = [json])]
7#[derive(Debug, Clone)]
8pub struct PayloadSignature {
9 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
11 pub authenticator_data: Vec<u8>,
12 pub client_data_json: String,
14
15 #[serde(flatten)]
16 pub signature: Signature,
17}
18
19impl PayloadSignature {
20 pub fn verify(
26 &self,
27 message: impl AsRef<[u8]>,
28 require_user_verification: bool,
29 ) -> Option<PublicKey> {
30 if self.authenticator_data.len() < 37
32 || !Self::verify_flags(self.authenticator_data[32], require_user_verification)
33 {
34 return None;
35 }
36
37 let c: CollectedClientData = serde_json::from_str(&self.client_data_json).ok()?;
39 if c.typ != ClientDataType::Get {
40 return None;
41 }
42
43 if c.challenge != message.as_ref() {
48 return None;
49 }
50
51 let hash = env::sha256_array(self.client_data_json.as_bytes());
54
55 self.signature
58 .verify(&[self.authenticator_data.as_slice(), hash.as_slice()].concat())
59 }
60
61 #[allow(clippy::identity_op)]
62 const AUTH_DATA_FLAGS_UP: u8 = 1 << 0;
63 const AUTH_DATA_FLAGS_UV: u8 = 1 << 2;
64 const AUTH_DATA_FLAGS_BE: u8 = 1 << 3;
65 const AUTH_DATA_FLAGS_BS: u8 = 1 << 4;
66
67 const fn verify_flags(flags: u8, require_user_verification: bool) -> bool {
69 if flags & Self::AUTH_DATA_FLAGS_UP != Self::AUTH_DATA_FLAGS_UP {
71 return false;
72 }
73
74 if require_user_verification
78 && (flags & Self::AUTH_DATA_FLAGS_UV != Self::AUTH_DATA_FLAGS_UV)
79 {
80 return false;
81 }
82
83 if (flags & Self::AUTH_DATA_FLAGS_BE != Self::AUTH_DATA_FLAGS_BE)
86 && (flags & Self::AUTH_DATA_FLAGS_BS == Self::AUTH_DATA_FLAGS_BS)
87 {
88 return false;
89 }
90
91 true
92 }
93}
94
95#[near(serializers = [json])]
97#[derive(Debug, Clone)]
98pub struct CollectedClientData {
99 #[serde(rename = "type")]
100 pub typ: ClientDataType,
101
102 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
103 pub challenge: Vec<u8>,
104
105 pub origin: String,
106}
107
108#[near(serializers = [json])]
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum ClientDataType {
111 #[serde(rename = "webauthn.create")]
113 Create,
114
115 #[serde(rename = "webauthn.get")]
117 Get,
118}
119
120#[near(serializers = [json])]
121#[serde(untagged)]
122#[derive(Debug, Clone)]
123pub enum Signature {
124 Ed25519 {
127 #[serde_as(as = "AsCurve<Ed25519>")]
128 public_key: <Ed25519 as Curve>::PublicKey,
129 #[serde_as(as = "AsCurve<Ed25519>")]
130 signature: <Ed25519 as Curve>::Signature,
131 },
132 P256 {
134 #[serde_as(as = "AsCurve<P256>")]
135 public_key: <P256 as Curve>::PublicKey,
136 #[serde_as(as = "AsCurve<P256>")]
137 signature: <P256 as Curve>::Signature,
138 },
139}
140
141impl Signature {
142 #[inline]
143 pub fn verify(&self, message: &[u8]) -> Option<PublicKey> {
144 match self {
145 Self::Ed25519 {
148 public_key,
149 signature,
150 } => Ed25519::verify(signature, message, public_key).map(PublicKey::Ed25519),
151 Self::P256 {
154 public_key,
155 signature,
156 } => {
157 let prehashed = env::sha256_array(message);
159 P256::verify(signature, &prehashed, public_key).map(PublicKey::P256)
160 }
161 }
162 }
163}