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#[cfg_attr(
7 all(feature = "abi", not(target_arch = "wasm32")),
8 serde_as(schemars = true)
9)]
10#[cfg_attr(
11 not(all(feature = "abi", not(target_arch = "wasm32"))),
12 serde_as(schemars = false)
13)]
14#[near(serializers = [json])]
15#[derive(Debug, Clone)]
16pub struct PayloadSignature {
17 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
19 pub authenticator_data: Vec<u8>,
20 pub client_data_json: String,
22
23 #[serde(flatten)]
24 pub signature: Signature,
25}
26
27impl PayloadSignature {
28 pub fn verify(
34 &self,
35 message: impl AsRef<[u8]>,
36 require_user_verification: bool,
37 ) -> Option<PublicKey> {
38 if self.authenticator_data.len() < 37
40 || !Self::verify_flags(self.authenticator_data[32], require_user_verification)
41 {
42 return None;
43 }
44
45 let c: CollectedClientData = serde_json::from_str(&self.client_data_json).ok()?;
47 if c.typ != ClientDataType::Get {
48 return None;
49 }
50
51 if c.challenge != message.as_ref() {
56 return None;
57 }
58
59 let hash = env::sha256_array(self.client_data_json.as_bytes());
62
63 self.signature
66 .verify(&[self.authenticator_data.as_slice(), hash.as_slice()].concat())
67 }
68
69 #[allow(clippy::identity_op)]
70 const AUTH_DATA_FLAGS_UP: u8 = 1 << 0;
71 const AUTH_DATA_FLAGS_UV: u8 = 1 << 2;
72 const AUTH_DATA_FLAGS_BE: u8 = 1 << 3;
73 const AUTH_DATA_FLAGS_BS: u8 = 1 << 4;
74
75 const fn verify_flags(flags: u8, require_user_verification: bool) -> bool {
77 if flags & Self::AUTH_DATA_FLAGS_UP != Self::AUTH_DATA_FLAGS_UP {
79 return false;
80 }
81
82 if require_user_verification
86 && (flags & Self::AUTH_DATA_FLAGS_UV != Self::AUTH_DATA_FLAGS_UV)
87 {
88 return false;
89 }
90
91 if (flags & Self::AUTH_DATA_FLAGS_BE != Self::AUTH_DATA_FLAGS_BE)
94 && (flags & Self::AUTH_DATA_FLAGS_BS == Self::AUTH_DATA_FLAGS_BS)
95 {
96 return false;
97 }
98
99 true
100 }
101}
102
103#[cfg_attr(
105 all(feature = "abi", not(target_arch = "wasm32")),
106 serde_as(schemars = true)
107)]
108#[cfg_attr(
109 not(all(feature = "abi", not(target_arch = "wasm32"))),
110 serde_as(schemars = false)
111)]
112#[near(serializers = [json])]
113#[derive(Debug, Clone)]
114pub struct CollectedClientData {
115 #[serde(rename = "type")]
116 pub typ: ClientDataType,
117
118 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
119 pub challenge: Vec<u8>,
120
121 pub origin: String,
122}
123
124#[near(serializers = [json])]
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum ClientDataType {
127 #[serde(rename = "webauthn.create")]
129 Create,
130
131 #[serde(rename = "webauthn.get")]
133 Get,
134}
135
136#[cfg_attr(
137 all(feature = "abi", not(target_arch = "wasm32")),
138 serde_as(schemars = true)
139)]
140#[cfg_attr(
141 not(all(feature = "abi", not(target_arch = "wasm32"))),
142 serde_as(schemars = false)
143)]
144#[near(serializers = [json])]
145#[serde(untagged)]
146#[derive(Debug, Clone)]
147pub enum Signature {
148 Ed25519 {
151 #[serde_as(as = "AsCurve<Ed25519>")]
152 public_key: <Ed25519 as Curve>::PublicKey,
153 #[serde_as(as = "AsCurve<Ed25519>")]
154 signature: <Ed25519 as Curve>::Signature,
155 },
156 P256 {
158 #[serde_as(as = "AsCurve<P256>")]
159 public_key: <P256 as Curve>::PublicKey,
160 #[serde_as(as = "AsCurve<P256>")]
161 signature: <P256 as Curve>::Signature,
162 },
163}
164
165impl Signature {
166 #[inline]
167 pub fn verify(&self, message: &[u8]) -> Option<PublicKey> {
168 match self {
169 Self::Ed25519 {
172 public_key,
173 signature,
174 } => Ed25519::verify(signature, message, public_key).map(PublicKey::Ed25519),
175 Self::P256 {
178 public_key,
179 signature,
180 } => {
181 let prehashed = env::sha256_array(message);
183 P256::verify(signature, &prehashed, public_key).map(PublicKey::P256)
184 }
185 }
186 }
187}