1use super::tempo_transaction::{
2 MAX_WEBAUTHN_SIGNATURE_LENGTH, P256_SIGNATURE_LENGTH, SECP256K1_SIGNATURE_LENGTH, SignatureType,
3};
4use alloc::vec::Vec;
5use alloy_primitives::{Address, B256, Bytes, Signature, U256, keccak256, uint};
6use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
7use sha2::{Digest, Sha256};
8
9use p256 as _;
11
12#[cfg(not(feature = "std"))]
13use once_cell::race::OnceBox as OnceLock;
14#[cfg(feature = "std")]
15use std::sync::OnceLock;
16
17pub const P256_ORDER: U256 =
19 uint!(0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551_U256);
20
21pub const P256N_HALF: U256 =
27 uint!(0x7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8_U256);
28
29pub fn normalize_p256_s(s_bytes: &[u8]) -> Result<B256, &'static str> {
40 let s = U256::from_be_slice(s_bytes);
41 if s.is_zero() || s >= P256_ORDER {
42 return Err("P256 s value out of range");
43 }
44 let normalized_s = if s > P256N_HALF { P256_ORDER - s } else { s };
45 Ok(B256::from(normalized_s.to_be_bytes::<32>()))
46}
47
48pub const SIGNATURE_TYPE_P256: u8 = 0x01;
51pub const SIGNATURE_TYPE_WEBAUTHN: u8 = 0x02;
52pub const SIGNATURE_TYPE_KEYCHAIN: u8 = 0x03;
53pub const SIGNATURE_TYPE_KEYCHAIN_V2: u8 = 0x04;
54
55const MIN_AUTH_DATA_LEN: usize = 37;
57
58const UP: u8 = 0x01; const UV: u8 = 0x04; const AT: u8 = 0x40; const ED: u8 = 0x80; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
69#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
70#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
71#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact))]
72pub struct P256SignatureWithPreHash {
73 pub r: B256,
74 pub s: B256,
75 pub pub_key_x: B256,
76 pub pub_key_y: B256,
77 pub pre_hash: bool,
78}
79
80#[derive(Clone, Debug, PartialEq, Eq, Hash)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
84#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
85#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
86#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact))]
87pub struct WebAuthnSignature {
88 pub r: B256,
89 pub s: B256,
90 pub pub_key_x: B256,
91 pub pub_key_y: B256,
92 pub webauthn_data: Bytes,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))]
105#[cfg_attr(
106 all(test, feature = "reth-codec"),
107 reth_codecs::add_arbitrary_tests(compact, rlp)
108)]
109#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
110pub enum PrimitiveSignature {
111 Secp256k1(Signature),
113
114 P256(P256SignatureWithPreHash),
116
117 WebAuthn(WebAuthnSignature),
119}
120
121impl PrimitiveSignature {
122 pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
128 if data.is_empty() {
129 return Err("Signature data is empty");
130 }
131
132 if data.len() == SECP256K1_SIGNATURE_LENGTH {
134 let sig = Signature::try_from(data)
135 .map_err(|_| "Failed to parse secp256k1 signature: invalid signature values")?;
136 return Ok(Self::Secp256k1(sig));
137 }
138
139 if data.len() < 2 {
141 return Err("Signature data too short: expected type identifier + signature data");
142 }
143
144 let type_id = data[0];
145 let sig_data = &data[1..];
146
147 match type_id {
148 SIGNATURE_TYPE_P256 => {
149 if sig_data.len() != P256_SIGNATURE_LENGTH {
150 return Err("Invalid P256 signature length");
151 }
152 Ok(Self::P256(P256SignatureWithPreHash {
153 r: B256::from_slice(&sig_data[0..32]),
154 s: B256::from_slice(&sig_data[32..64]),
155 pub_key_x: B256::from_slice(&sig_data[64..96]),
156 pub_key_y: B256::from_slice(&sig_data[96..128]),
157 pre_hash: sig_data[128] != 0,
158 }))
159 }
160 SIGNATURE_TYPE_WEBAUTHN => {
161 let len = sig_data.len();
162 if !(128..=MAX_WEBAUTHN_SIGNATURE_LENGTH).contains(&len) {
163 return Err("Invalid WebAuthn signature length");
164 }
165 Ok(Self::WebAuthn(WebAuthnSignature {
166 r: B256::from_slice(&sig_data[len - 128..len - 96]),
167 s: B256::from_slice(&sig_data[len - 96..len - 64]),
168 pub_key_x: B256::from_slice(&sig_data[len - 64..len - 32]),
169 pub_key_y: B256::from_slice(&sig_data[len - 32..]),
170 webauthn_data: Bytes::copy_from_slice(&sig_data[..len - 128]),
171 }))
172 }
173
174 _ => Err("Unknown signature type identifier"),
175 }
176 }
177
178 pub fn to_bytes(&self) -> Bytes {
184 match self {
185 Self::Secp256k1(sig) => {
186 let sig_bytes = sig.as_bytes();
189 assert_eq!(
190 sig_bytes.len(),
191 SECP256K1_SIGNATURE_LENGTH,
192 "Secp256k1 signature must be exactly 65 bytes"
193 );
194 Bytes::copy_from_slice(&sig_bytes)
195 }
196 Self::P256(p256_sig) => {
197 let mut bytes = Vec::with_capacity(1 + 129);
198 bytes.push(SIGNATURE_TYPE_P256);
199 bytes.extend_from_slice(p256_sig.r.as_slice());
200 bytes.extend_from_slice(p256_sig.s.as_slice());
201 bytes.extend_from_slice(p256_sig.pub_key_x.as_slice());
202 bytes.extend_from_slice(p256_sig.pub_key_y.as_slice());
203 bytes.push(if p256_sig.pre_hash { 1 } else { 0 });
204 Bytes::from(bytes)
205 }
206 Self::WebAuthn(webauthn_sig) => {
207 let mut bytes = Vec::with_capacity(1 + webauthn_sig.webauthn_data.len() + 128);
208 bytes.push(SIGNATURE_TYPE_WEBAUTHN);
209 bytes.extend_from_slice(&webauthn_sig.webauthn_data);
210 bytes.extend_from_slice(webauthn_sig.r.as_slice());
211 bytes.extend_from_slice(webauthn_sig.s.as_slice());
212 bytes.extend_from_slice(webauthn_sig.pub_key_x.as_slice());
213 bytes.extend_from_slice(webauthn_sig.pub_key_y.as_slice());
214 Bytes::from(bytes)
215 }
216 }
217 }
218
219 pub fn encoded_length(&self) -> usize {
225 match self {
226 Self::Secp256k1(_) => SECP256K1_SIGNATURE_LENGTH,
227 Self::P256(_) => 1 + P256_SIGNATURE_LENGTH,
228 Self::WebAuthn(webauthn_sig) => 1 + webauthn_sig.webauthn_data.len() + 128,
229 }
230 }
231
232 pub fn signature_type(&self) -> SignatureType {
234 match self {
235 Self::Secp256k1(_) => SignatureType::Secp256k1,
236 Self::P256(_) => SignatureType::P256,
237 Self::WebAuthn(_) => SignatureType::WebAuthn,
238 }
239 }
240
241 pub fn size(&self) -> usize {
243 size_of::<Self>()
244 + match self {
245 Self::Secp256k1(_) | Self::P256(_) => 0,
246 Self::WebAuthn(webauthn_sig) => webauthn_sig.webauthn_data.len(),
247 }
248 }
249
250 pub fn recover_signer(
257 &self,
258 sig_hash: &B256,
259 ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
260 match self {
261 Self::Secp256k1(sig) => {
262 alloy_consensus::crypto::secp256k1::recover_signer(sig, *sig_hash)
265 }
266 Self::P256(p256_sig) => {
267 let message_hash = if p256_sig.pre_hash {
269 B256::from_slice(Sha256::digest(sig_hash).as_ref())
271 } else {
272 *sig_hash
273 };
274
275 verify_p256_signature_internal(
277 p256_sig.r.as_slice(),
278 p256_sig.s.as_slice(),
279 p256_sig.pub_key_x.as_slice(),
280 p256_sig.pub_key_y.as_slice(),
281 &message_hash,
282 )
283 .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
284
285 Ok(derive_p256_address(
287 &p256_sig.pub_key_x,
288 &p256_sig.pub_key_y,
289 ))
290 }
291 Self::WebAuthn(webauthn_sig) => {
292 let message_hash =
294 verify_webauthn_data_internal(&webauthn_sig.webauthn_data, sig_hash)
295 .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
296
297 verify_p256_signature_internal(
299 webauthn_sig.r.as_slice(),
300 webauthn_sig.s.as_slice(),
301 webauthn_sig.pub_key_x.as_slice(),
302 webauthn_sig.pub_key_y.as_slice(),
303 &message_hash,
304 )
305 .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
306
307 Ok(derive_p256_address(
309 &webauthn_sig.pub_key_x,
310 &webauthn_sig.pub_key_y,
311 ))
312 }
313 }
314 }
315}
316
317impl Default for PrimitiveSignature {
318 fn default() -> Self {
319 Self::Secp256k1(Signature::test_signature())
320 }
321}
322
323impl alloy_rlp::Encodable for PrimitiveSignature {
324 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
325 let bytes = self.to_bytes();
326 alloy_rlp::Encodable::encode(&bytes, out);
327 }
328
329 fn length(&self) -> usize {
330 alloy_rlp::Header {
331 list: false,
332 payload_length: self.encoded_length(),
333 }
334 .length_with_payload()
335 }
336}
337
338impl alloy_rlp::Decodable for PrimitiveSignature {
339 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
340 let bytes: Bytes = alloy_rlp::Decodable::decode(buf)?;
341 Self::from_bytes(&bytes).map_err(alloy_rlp::Error::Custom)
342 }
343}
344
345#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
349#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
350#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
351#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
352pub enum KeychainVersion {
353 #[default]
357 V1,
358 V2,
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368pub enum KeychainVersionError {
369 LegacyPostT1C,
371 V2BeforeActivation,
373}
374
375#[derive(Clone, Debug)]
389#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
390#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
391pub struct KeychainSignature {
392 pub user_address: Address,
394 pub signature: PrimitiveSignature,
396 #[cfg_attr(feature = "serde", serde(default))]
398 pub version: KeychainVersion,
399 #[cfg_attr(
404 feature = "serde",
405 serde(
406 serialize_with = "serialize_once_lock",
407 rename = "keyId",
408 skip_deserializing,
409 )
410 )]
411 cached_key_id: OnceLock<Address>,
412}
413
414impl KeychainSignature {
415 pub fn new(user_address: Address, signature: PrimitiveSignature) -> Self {
419 Self {
420 user_address,
421 signature,
422 version: KeychainVersion::V2,
423 cached_key_id: OnceLock::new(),
424 }
425 }
426
427 pub fn new_v1(user_address: Address, signature: PrimitiveSignature) -> Self {
432 Self {
433 user_address,
434 signature,
435 version: KeychainVersion::V1,
436 cached_key_id: OnceLock::new(),
437 }
438 }
439
440 fn effective_sig_hash(&self, sig_hash: &B256) -> B256 {
445 match self.version {
446 KeychainVersion::V1 => *sig_hash,
447 KeychainVersion::V2 => Self::signing_hash(*sig_hash, self.user_address),
448 }
449 }
450
451 pub fn key_id(
459 &self,
460 sig_hash: &B256,
461 ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
462 if let Some(cached) = self.cached_key_id.get() {
464 return Ok(*cached);
465 }
466
467 let effective_hash = self.effective_sig_hash(sig_hash);
469 let key_id = self.signature.recover_signer(&effective_hash)?;
470 #[allow(clippy::useless_conversion)]
471 let _ = self.cached_key_id.set(key_id.into());
472 Ok(key_id)
473 }
474
475 pub fn is_legacy(&self) -> bool {
477 self.version == KeychainVersion::V1
478 }
479
480 pub fn signing_hash(sig_hash: B256, user_address: Address) -> B256 {
487 let mut buf = [0u8; 53]; buf[0] = SIGNATURE_TYPE_KEYCHAIN_V2;
489 buf[1..33].copy_from_slice(sig_hash.as_slice());
490 buf[33..].copy_from_slice(user_address.as_slice());
491 keccak256(buf)
492 }
493}
494
495impl PartialEq for KeychainSignature {
498 fn eq(&self, other: &Self) -> bool {
499 self.user_address == other.user_address
500 && self.signature == other.signature
501 && self.version == other.version
502 }
503}
504
505impl Eq for KeychainSignature {}
506
507impl core::hash::Hash for KeychainSignature {
508 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
509 self.user_address.hash(state);
510 self.signature.hash(state);
511 self.version.hash(state);
512 }
513}
514
515#[cfg(any(test, feature = "arbitrary"))]
517impl<'a> arbitrary::Arbitrary<'a> for KeychainSignature {
518 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
519 Ok(Self {
520 user_address: u.arbitrary()?,
521 signature: u.arbitrary()?,
522 version: u.arbitrary()?,
523 cached_key_id: OnceLock::new(), })
525 }
526}
527
528#[derive(Clone, Debug, PartialEq, Eq, Hash)]
532#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
533#[cfg_attr(feature = "serde", serde(untagged, rename_all = "camelCase"))]
534#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
535#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
536pub enum TempoSignature {
537 Primitive(PrimitiveSignature),
539
540 Keychain(KeychainSignature),
545}
546
547impl TempoSignature {
548 pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
554 if data.is_empty() {
555 return Err("Signature data is empty");
556 }
557
558 if data.len() > 1
561 && data.len() != SECP256K1_SIGNATURE_LENGTH
562 && (data[0] == SIGNATURE_TYPE_KEYCHAIN || data[0] == SIGNATURE_TYPE_KEYCHAIN_V2)
563 {
564 let version = if data[0] == SIGNATURE_TYPE_KEYCHAIN {
565 KeychainVersion::V1
566 } else {
567 KeychainVersion::V2
568 };
569 let sig_data = &data[1..];
570
571 if sig_data.len() < 20 {
573 return Err("Invalid Keychain signature: too short for user_address");
574 }
575
576 let user_address = Address::from_slice(&sig_data[0..20]);
577 let inner_sig_bytes = &sig_data[20..];
578
579 let inner_signature = PrimitiveSignature::from_bytes(inner_sig_bytes)?;
582
583 return Ok(Self::Keychain(KeychainSignature {
584 user_address,
585 signature: inner_signature,
586 version,
587 cached_key_id: OnceLock::new(),
588 }));
589 }
590
591 let primitive = PrimitiveSignature::from_bytes(data)?;
593 Ok(Self::Primitive(primitive))
594 }
595
596 pub fn to_bytes(&self) -> Bytes {
602 match self {
603 Self::Primitive(primitive_sig) => primitive_sig.to_bytes(),
604 Self::Keychain(keychain_sig) => {
605 let inner_bytes = keychain_sig.signature.to_bytes();
607 let mut bytes = Vec::with_capacity(1 + 20 + inner_bytes.len());
608 let type_byte = match keychain_sig.version {
609 KeychainVersion::V1 => SIGNATURE_TYPE_KEYCHAIN,
610 KeychainVersion::V2 => SIGNATURE_TYPE_KEYCHAIN_V2,
611 };
612 bytes.push(type_byte);
613 bytes.extend_from_slice(keychain_sig.user_address.as_slice());
614 bytes.extend_from_slice(&inner_bytes);
615 Bytes::from(bytes)
616 }
617 }
618 }
619
620 pub fn encoded_length(&self) -> usize {
626 match self {
627 Self::Primitive(primitive_sig) => primitive_sig.encoded_length(),
628 Self::Keychain(keychain_sig) => 1 + 20 + keychain_sig.signature.encoded_length(),
629 }
630 }
631
632 pub fn signature_type(&self) -> SignatureType {
634 match self {
635 Self::Primitive(primitive_sig) => primitive_sig.signature_type(),
636 Self::Keychain(keychain_sig) => keychain_sig.signature.signature_type(),
637 }
638 }
639
640 pub fn size(&self) -> usize {
642 match self {
643 Self::Primitive(primitive_sig) => primitive_sig.size(),
644 Self::Keychain(keychain_sig) => 1 + 20 + keychain_sig.signature.size(),
645 }
646 }
647
648 pub fn recover_signer(
663 &self,
664 sig_hash: &B256,
665 ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
666 match self {
667 Self::Primitive(primitive_sig) => primitive_sig.recover_signer(sig_hash),
668 Self::Keychain(keychain_sig) => {
669 keychain_sig.key_id(sig_hash)?;
671
672 Ok(keychain_sig.user_address)
674 }
675 }
676 }
677
678 pub fn is_keychain(&self) -> bool {
680 matches!(self, Self::Keychain(_))
681 }
682
683 pub fn is_legacy_keychain(&self) -> bool {
685 matches!(self, Self::Keychain(k) if k.is_legacy())
686 }
687
688 pub fn is_v2_keychain(&self) -> bool {
690 matches!(
691 self,
692 Self::Keychain(KeychainSignature {
693 version: KeychainVersion::V2,
694 ..
695 })
696 )
697 }
698
699 pub fn validate_version(&self, is_t1c: bool) -> Result<(), KeychainVersionError> {
704 if is_t1c && self.is_legacy_keychain() {
705 return Err(KeychainVersionError::LegacyPostT1C);
706 }
707 if !is_t1c && self.is_v2_keychain() {
708 return Err(KeychainVersionError::V2BeforeActivation);
709 }
710 Ok(())
711 }
712
713 pub fn as_keychain(&self) -> Option<&KeychainSignature> {
715 match self {
716 Self::Keychain(keychain_sig) => Some(keychain_sig),
717 _ => None,
718 }
719 }
720}
721
722impl Default for TempoSignature {
723 fn default() -> Self {
724 Self::Primitive(PrimitiveSignature::default())
725 }
726}
727
728impl alloy_rlp::Encodable for TempoSignature {
729 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
730 let bytes = self.to_bytes();
731 alloy_rlp::Encodable::encode(&bytes, out);
732 }
733
734 fn length(&self) -> usize {
735 alloy_rlp::Header {
736 list: false,
737 payload_length: self.encoded_length(),
738 }
739 .length_with_payload()
740 }
741}
742
743impl alloy_rlp::Decodable for TempoSignature {
744 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
745 let bytes: Bytes = alloy_rlp::Decodable::decode(buf)?;
746 Self::from_bytes(&bytes).map_err(alloy_rlp::Error::Custom)
747 }
748}
749
750impl From<Signature> for TempoSignature {
751 fn from(signature: Signature) -> Self {
752 Self::Primitive(PrimitiveSignature::Secp256k1(signature))
753 }
754}
755
756pub fn derive_p256_address(pub_key_x: &B256, pub_key_y: &B256) -> Address {
762 let hash = keccak256([pub_key_x.as_slice(), pub_key_y.as_slice()].concat());
763
764 Address::from_slice(&hash[12..])
766}
767
768fn concat<const N: usize>(slices: &[&[u8]]) -> [u8; N] {
770 let mut out = [0u8; N];
771 let mut offset = 0;
772 for s in slices {
773 out[offset..offset + s.len()].copy_from_slice(s);
774 offset += s.len();
775 }
776 debug_assert_eq!(offset, N, "slices length doesn't match array size");
777 out
778}
779
780#[cfg(feature = "std")]
781fn verify_p256_signature_with_aws_lc(
782 r: &[u8],
783 s: &[u8],
784 pub_key_x: &[u8],
785 pub_key_y: &[u8],
786 message_hash: &B256,
787) -> Result<(), &'static str> {
788 use aws_lc_rs::{
789 digest::{Digest as AwsLcDigest, SHA256 as AwsLcSha256},
790 signature::{ECDSA_P256_SHA256_FIXED, ParsedPublicKey as AwsLcParsedPublicKey},
791 };
792
793 let encoded_point = concat::<65>(&[&[0x04], pub_key_x, pub_key_y]);
794 let verifying_key = AwsLcParsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, encoded_point)
795 .map_err(|_| "Invalid P256 public key")?;
796
797 let signature = concat::<64>(&[r, s]);
798
799 let digest = AwsLcDigest::import_less_safe(message_hash.as_slice(), &AwsLcSha256)
802 .map_err(|_| "Invalid P256 message digest")?;
803
804 verifying_key
805 .verify_digest_sig(&digest, &signature)
806 .map_err(|_| "P256 signature verification failed")
807}
808
809#[cfg(any(test, not(feature = "std")))]
810fn verify_p256_signature_with_p256(
811 r: &[u8],
812 s: &[u8],
813 pub_key_x: &[u8],
814 pub_key_y: &[u8],
815 message_hash: &B256,
816) -> Result<(), &'static str> {
817 use p256::{
818 EncodedPoint,
819 ecdsa::{Signature as P256Signature, VerifyingKey, signature::hazmat::PrehashVerifier},
820 };
821
822 let encoded_point =
823 EncodedPoint::from_affine_coordinates(pub_key_x.into(), pub_key_y.into(), false);
824 let verifying_key =
825 VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| "Invalid P256 public key")?;
826
827 let signature = P256Signature::from_slice(&concat::<64>(&[r, s]))
828 .map_err(|_| "Invalid P256 signature encoding")?;
829
830 verifying_key
832 .verify_prehash(message_hash.as_slice(), &signature)
833 .map_err(|_| "P256 signature verification failed")
834}
835
836fn verify_p256_signature_internal(
852 r: &[u8],
853 s: &[u8],
854 pub_key_x: &[u8],
855 pub_key_y: &[u8],
856 message_hash: &B256,
857) -> Result<(), &'static str> {
858 if U256::from_be_slice(s) > P256N_HALF {
860 return Err("P256 signature has high s value");
861 }
862
863 #[cfg(all(feature = "std", not(test)))]
864 {
865 verify_p256_signature_with_aws_lc(r, s, pub_key_x, pub_key_y, message_hash)
867 }
868
869 #[cfg(not(feature = "std"))]
870 {
871 verify_p256_signature_with_p256(r, s, pub_key_x, pub_key_y, message_hash)
873 }
874
875 #[cfg(all(feature = "std", test))]
876 {
877 let aws_lc = verify_p256_signature_with_aws_lc(r, s, pub_key_x, pub_key_y, message_hash);
879 let p256 = verify_p256_signature_with_p256(r, s, pub_key_x, pub_key_y, message_hash);
880
881 assert_eq!(
882 aws_lc.is_ok(),
883 p256.is_ok(),
884 "aws-lc and p256 verification backends disagreed"
885 );
886
887 aws_lc
888 }
889}
890
891#[derive(serde::Deserialize)]
894struct ClientDataJson<'a> {
895 #[serde(rename = "type")]
896 type_field: &'a str,
897 challenge: &'a str,
898}
899
900fn verify_webauthn_data_internal(
908 webauthn_data: &[u8],
909 tx_hash: &B256,
910) -> Result<B256, &'static str> {
911 if webauthn_data.len() < MIN_AUTH_DATA_LEN + 32 {
913 return Err("WebAuthn data too short");
914 }
915
916 let flags = webauthn_data[32];
918 let (up_flag, uv_flag, at_flag, ed_flag) = (flags & UP, flags & UV, flags & AT, flags & ED);
919
920 if up_flag == 0 && uv_flag == 0 {
922 return Err("neither UP, nor UV flag set");
923 }
924
925 if at_flag != 0 {
927 return Err("AT flag must not be set for assertion signatures");
928 }
929
930 let auth_data_len = if ed_flag == 0 {
932 MIN_AUTH_DATA_LEN
934 } else {
935 return Err("ED flag must not be set, as Tempo doesn't support extensions");
938 };
939
940 let authenticator_data = &webauthn_data[..auth_data_len];
941 let client_data_json = &webauthn_data[auth_data_len..];
942
943 let client_data: ClientDataJson<'_> =
946 serde_json::from_slice(client_data_json).map_err(|_| "clientDataJSON is not valid JSON")?;
947
948 if client_data.type_field != "webauthn.get" {
950 return Err("clientDataJSON type must be webauthn.get");
951 }
952
953 if client_data.challenge != URL_SAFE_NO_PAD.encode(tx_hash.as_slice()) {
955 return Err("clientDataJSON challenge does not match transaction hash");
956 }
957
958 let client_data_hash = Sha256::digest(client_data_json);
961
962 let mut final_hasher = Sha256::new();
963 final_hasher.update(authenticator_data);
964 final_hasher.update(client_data_hash);
965 let message_hash = final_hasher.finalize();
966
967 Ok(B256::from_slice(&message_hash))
968}
969
970#[cfg(feature = "serde")]
971fn serialize_once_lock<S>(value: &OnceLock<Address>, serializer: S) -> Result<S::Ok, S::Error>
973where
974 S: serde::Serializer,
975{
976 serde::Serialize::serialize(&value.get(), serializer)
977}
978
979#[cfg(test)]
980mod tests {
981 use super::*;
982 use alloy_primitives::hex;
983 use alloy_rlp::Encodable;
984 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
985 use p256::{
986 ecdsa::{SigningKey as P256SigningKey, signature::hazmat::PrehashSigner},
987 elliptic_curve::rand_core::OsRng,
988 };
989 use proptest::prelude::*;
990 use proptest_arbitrary_interop::arb;
991
992 fn generate_p256_keypair() -> (P256SigningKey, B256, B256) {
994 let signing_key = P256SigningKey::random(&mut OsRng);
995 let verifying_key = signing_key.verifying_key();
996 let encoded_point = verifying_key.to_encoded_point(false);
997 let pub_key_x = B256::from_slice(encoded_point.x().unwrap().as_ref());
998 let pub_key_y = B256::from_slice(encoded_point.y().unwrap().as_ref());
999 (signing_key, pub_key_x, pub_key_y)
1000 }
1001
1002 fn sign_p256_normalized(signing_key: &P256SigningKey, message_hash: &B256) -> (B256, B256) {
1004 let signature: p256::ecdsa::Signature =
1005 signing_key.sign_prehash(message_hash.as_slice()).unwrap();
1006 let sig_bytes = signature.to_bytes();
1007 let r = B256::from_slice(&sig_bytes[0..32]);
1008 let s = normalize_p256_s(&sig_bytes[32..64]).expect("p256 crate produces valid s");
1009 (r, s)
1010 }
1011
1012 fn build_webauthn_data(flags: u8, extension: Option<&[u8]>, tx_hash: &B256) -> Vec<u8> {
1014 let mut data = vec![0u8; 32]; data.push(flags);
1016 data.extend_from_slice(&[0u8; 4]); if let Some(ext) = extension {
1018 data.extend_from_slice(ext);
1019 }
1020 let challenge = URL_SAFE_NO_PAD.encode(tx_hash.as_slice());
1021 data.extend_from_slice(
1022 format!("{{\"type\":\"webauthn.get\",\"challenge\":\"{challenge}\"}}").as_bytes(),
1023 );
1024 data
1025 }
1026
1027 proptest! {
1028 #[test]
1029 fn proptest_primitive_signature_rlp_length_matches_to_bytes(signature in arb::<PrimitiveSignature>()) {
1030 prop_assert_eq!(signature.to_bytes().length(), signature.length());
1031 }
1032
1033 #[test]
1034 fn proptest_tempo_signature_rlp_length_matches_to_bytes(signature in arb::<TempoSignature>()) {
1035 prop_assert_eq!(signature.to_bytes().length(), signature.length());
1036 }
1037 }
1038
1039 #[test]
1040 fn test_p256_high_s_normalization() {
1041 let low_s = U256::from(1u64);
1043 let low_s_bytes: [u8; 32] = low_s.to_be_bytes();
1044 assert_eq!(
1045 U256::from_be_slice(normalize_p256_s(&low_s_bytes).unwrap().as_slice()),
1046 low_s,
1047 "s < P256N_HALF should remain unchanged"
1048 );
1049
1050 let half_bytes: [u8; 32] = P256N_HALF.to_be_bytes();
1052 assert_eq!(
1053 U256::from_be_slice(normalize_p256_s(&half_bytes).unwrap().as_slice()),
1054 P256N_HALF,
1055 "s == P256N_HALF should remain unchanged"
1056 );
1057
1058 let high_s = P256N_HALF + U256::from(1u64);
1060 let high_s_bytes: [u8; 32] = high_s.to_be_bytes();
1061 assert_eq!(
1062 U256::from_be_slice(normalize_p256_s(&high_s_bytes).unwrap().as_slice()),
1063 P256_ORDER - high_s,
1064 "s > P256N_HALF should be normalized"
1065 );
1066
1067 let max_s = P256_ORDER - U256::from(1u64);
1069 let max_s_bytes: [u8; 32] = max_s.to_be_bytes();
1070 assert_eq!(
1071 U256::from_be_slice(normalize_p256_s(&max_s_bytes).unwrap().as_slice()),
1072 U256::from(1u64),
1073 "s == P256_ORDER - 1 should normalize to 1"
1074 );
1075
1076 let zero_bytes: [u8; 32] = U256::ZERO.to_be_bytes();
1078 assert!(
1079 normalize_p256_s(&zero_bytes).is_err(),
1080 "s == 0 should be rejected"
1081 );
1082
1083 let order_bytes: [u8; 32] = P256_ORDER.to_be_bytes();
1085 assert!(
1086 normalize_p256_s(&order_bytes).is_err(),
1087 "s == P256_ORDER should be rejected"
1088 );
1089
1090 let over_bytes: [u8; 32] = (P256_ORDER + U256::from(1u64)).to_be_bytes();
1092 assert!(
1093 normalize_p256_s(&over_bytes).is_err(),
1094 "s > P256_ORDER should be rejected"
1095 );
1096
1097 let max_bytes: [u8; 32] = U256::MAX.to_be_bytes();
1099 assert!(
1100 normalize_p256_s(&max_bytes).is_err(),
1101 "s == U256::MAX should be rejected"
1102 );
1103 }
1104
1105 #[test]
1106 fn test_p256_signature_verification_invalid_pubkey() {
1107 let r = [0u8; 32];
1109 let s = [0u8; 32];
1110 let pub_key_x = [0u8; 32]; let pub_key_y = [0u8; 32];
1112 let message_hash = B256::ZERO;
1113
1114 let result = verify_p256_signature_internal(&r, &s, &pub_key_x, &pub_key_y, &message_hash);
1115 assert!(result.is_err());
1116 }
1117
1118 #[test]
1119 fn test_p256_signature_verification_invalid_signature() {
1120 let (_, pub_key_x, pub_key_y) = generate_p256_keypair();
1121 let message_hash = B256::ZERO;
1122
1123 let assert_invalid = |r: &[u8], s: &[u8], context: &str| {
1124 let result = verify_p256_signature_internal(
1125 r,
1126 s,
1127 pub_key_x.as_slice(),
1128 pub_key_y.as_slice(),
1129 &message_hash,
1130 );
1131 assert!(result.is_err(), "{context} should fail verification");
1132 };
1133
1134 let r = [0u8; 32];
1136 let s = [0u8; 32];
1137 assert_invalid(&r, &s, "all-zero signature");
1138
1139 let one = U256::from(1u64).to_be_bytes::<32>();
1140 let order = P256_ORDER.to_be_bytes::<32>();
1141 assert_invalid(&order, &one, "signature with r == P256_ORDER");
1142 assert_invalid(&one, &order, "signature with s == P256_ORDER");
1143 }
1144
1145 #[test]
1146 fn test_p256_signature_verification_valid() {
1147 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1148 let message_hash = B256::from_slice(&Sha256::digest(b"test message"));
1149 let (r, s) = sign_p256_normalized(&signing_key, &message_hash);
1150
1151 let result = verify_p256_signature_internal(
1152 r.as_slice(),
1153 s.as_slice(),
1154 pub_key_x.as_slice(),
1155 pub_key_y.as_slice(),
1156 &message_hash,
1157 );
1158 assert!(
1159 result.is_ok(),
1160 "Valid P256 signature should verify successfully"
1161 );
1162 }
1163
1164 #[test]
1165 fn test_p256_high_s_rejection() {
1166 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1167 let message_hash = B256::from_slice(&Sha256::digest(b"test message for high s"));
1168
1169 let signature: p256::ecdsa::Signature =
1171 signing_key.sign_prehash(message_hash.as_slice()).unwrap();
1172 let sig_bytes = signature.to_bytes();
1173 let r = &sig_bytes[0..32];
1174 let original_s = &sig_bytes[32..64];
1175
1176 let s_value = alloy_primitives::U256::from_be_slice(original_s);
1178 let computed_high_s = P256_ORDER - s_value;
1179 let computed_high_s_bytes: [u8; 32] = computed_high_s.to_be_bytes();
1180
1181 let s_is_low = s_value <= P256N_HALF;
1184 if s_is_low {
1185 let result = verify_p256_signature_internal(
1187 r,
1188 &computed_high_s_bytes,
1189 pub_key_x.as_slice(),
1190 pub_key_y.as_slice(),
1191 &message_hash,
1192 );
1193 assert!(
1194 result.is_err(),
1195 "High-s signature should be rejected for signature malleability prevention"
1196 );
1197 assert_eq!(result.unwrap_err(), "P256 signature has high s value");
1198 } else {
1199 let original_result = verify_p256_signature_internal(
1202 r,
1203 original_s,
1204 pub_key_x.as_slice(),
1205 pub_key_y.as_slice(),
1206 &message_hash,
1207 );
1208 assert!(
1209 original_result.is_err(),
1210 "Original high-s signature should be rejected"
1211 );
1212 }
1213 }
1214
1215 #[test]
1216 fn test_webauthn_data_verification_too_short() {
1217 let short_data = vec![0u8; 36];
1219 let tx_hash = B256::ZERO;
1220
1221 let result = verify_webauthn_data_internal(&short_data, &tx_hash);
1222 assert!(result.is_err());
1223 assert_eq!(result.unwrap_err(), "WebAuthn data too short");
1224 }
1225
1226 #[test]
1227 fn test_webauthn_data_verification_missing_up_and_uv_flags() {
1228 let tx_hash = B256::ZERO;
1229 let client_data = b"{\"type\":\"webauthn.get\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
1230
1231 let mut auth_data = vec![0u8; 37];
1233 auth_data[32] = 0x00;
1234 let mut webauthn_data = auth_data;
1235 webauthn_data.extend_from_slice(client_data);
1236
1237 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1238 assert!(result.is_err());
1239 assert_eq!(result.unwrap_err(), "neither UP, nor UV flag set");
1240
1241 let mut auth_data = vec![0u8; 37];
1243 auth_data[32] = 0x04;
1244 let mut webauthn_data = auth_data;
1245 webauthn_data.extend_from_slice(client_data);
1246
1247 assert!(verify_webauthn_data_internal(&webauthn_data, &tx_hash).is_ok());
1248 }
1249
1250 #[test]
1251 fn test_webauthn_data_verification_invalid_type() {
1252 let mut auth_data = vec![0u8; 37];
1254 auth_data[32] = 0x01; let client_data = b"{\"type\":\"webauthn.create\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
1258 let mut webauthn_data = auth_data;
1259 webauthn_data.extend_from_slice(client_data);
1260
1261 let tx_hash = B256::ZERO;
1262 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1263
1264 assert!(result.is_err());
1265 assert_eq!(
1266 result.unwrap_err(),
1267 "clientDataJSON type must be webauthn.get"
1268 );
1269 }
1270
1271 #[test]
1272 fn test_webauthn_data_verification_invalid_challenge() {
1273 let mut auth_data = vec![0u8; 37];
1275 auth_data[32] = 0x01; let client_data =
1279 b"{\"type\":\"webauthn.get\",\"challenge\":\"wrong_challenge_value_here\"}";
1280 let mut webauthn_data = auth_data;
1281 webauthn_data.extend_from_slice(client_data);
1282
1283 let tx_hash = B256::ZERO;
1284 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1285
1286 assert!(result.is_err());
1287 assert_eq!(
1288 result.unwrap_err(),
1289 "clientDataJSON challenge does not match transaction hash"
1290 );
1291 }
1292
1293 #[test]
1294 fn test_webauthn_data_verification_valid() {
1295 let tx_hash = B256::from_slice(&[0xAA; 32]);
1296 let webauthn_data = build_webauthn_data(0x01, None, &tx_hash); let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1299 assert!(
1300 result.is_ok(),
1301 "Valid WebAuthn data should verify successfully"
1302 );
1303
1304 let message_hash = result.unwrap();
1306 let auth_data = &webauthn_data[..37];
1307 let client_data = &webauthn_data[37..];
1308
1309 let client_data_hash = Sha256::digest(client_data);
1310 let mut final_hasher = Sha256::new();
1311 final_hasher.update(auth_data);
1312 final_hasher.update(client_data_hash);
1313 let expected_hash = final_hasher.finalize();
1314
1315 assert_eq!(message_hash.as_slice(), &expected_hash[..]);
1316 }
1317
1318 #[test]
1319 fn test_p256_address_derivation() {
1320 let pub_key_x =
1321 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1322 let pub_key_y =
1323 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1324
1325 let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1326 let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1327
1328 assert_eq!(addr1, addr2);
1330
1331 assert_ne!(addr1, Address::ZERO);
1333 }
1334
1335 #[test]
1336 fn test_p256_address_derivation_deterministic() {
1337 let pub_key_x =
1339 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1340 let pub_key_y =
1341 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1342
1343 let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1344 let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1345
1346 assert_eq!(addr1, addr2, "Address derivation should be deterministic");
1347 }
1348
1349 #[test]
1350 fn test_p256_address_different_keys_different_addresses() {
1351 let pub_key_x1 =
1353 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1354 let pub_key_y1 =
1355 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1356
1357 let pub_key_x2 =
1358 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1359 let pub_key_y2 =
1360 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1361
1362 let addr1 = derive_p256_address(&pub_key_x1, &pub_key_y1);
1363 let addr2 = derive_p256_address(&pub_key_x2, &pub_key_y2);
1364
1365 assert_ne!(
1366 addr1, addr2,
1367 "Different keys should produce different addresses"
1368 );
1369 }
1370
1371 #[test]
1372 fn test_tempo_signature_from_bytes_secp256k1() {
1373 use super::SECP256K1_SIGNATURE_LENGTH;
1374
1375 let sig_bytes = vec![0u8; SECP256K1_SIGNATURE_LENGTH];
1377 let result = TempoSignature::from_bytes(&sig_bytes);
1378
1379 assert!(result.is_ok());
1380 if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(_)) = result.unwrap() {
1381 } else {
1383 panic!("Expected Primitive(Secp256k1) variant");
1384 }
1385 }
1386
1387 #[test]
1388 fn test_tempo_signature_from_bytes_p256() {
1389 use super::{P256_SIGNATURE_LENGTH, SIGNATURE_TYPE_P256};
1390
1391 let mut sig_bytes = vec![SIGNATURE_TYPE_P256];
1392 sig_bytes.extend_from_slice(&[0u8; P256_SIGNATURE_LENGTH]);
1393 let result = TempoSignature::from_bytes(&sig_bytes);
1394
1395 assert!(result.is_ok());
1396 if let TempoSignature::Primitive(PrimitiveSignature::P256(_)) = result.unwrap() {
1397 } else {
1399 panic!("Expected Primitive(P256) variant");
1400 }
1401 }
1402
1403 #[test]
1404 fn test_tempo_signature_from_bytes_webauthn() {
1405 use super::SIGNATURE_TYPE_WEBAUTHN;
1406
1407 let mut sig_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1408 sig_bytes.extend_from_slice(&[0u8; 200]); let result = TempoSignature::from_bytes(&sig_bytes);
1410
1411 assert!(result.is_ok());
1412 if let TempoSignature::Primitive(PrimitiveSignature::WebAuthn(_)) = result.unwrap() {
1413 } else {
1415 panic!("Expected Primitive(WebAuthn) variant");
1416 }
1417 }
1418
1419 #[test]
1420 fn test_tempo_signature_from_bytes_validation() {
1421 assert_eq!(
1423 TempoSignature::from_bytes(&[]).unwrap_err(),
1424 "Signature data is empty"
1425 );
1426 assert_eq!(
1427 PrimitiveSignature::from_bytes(&[]).unwrap_err(),
1428 "Signature data is empty"
1429 );
1430
1431 assert_eq!(
1433 TempoSignature::from_bytes(&[0x01]).unwrap_err(),
1434 "Signature data too short: expected type identifier + signature data"
1435 );
1436
1437 let mut bad_p256 = vec![SIGNATURE_TYPE_P256];
1439 bad_p256.extend_from_slice(&[0u8; 100]); assert_eq!(
1441 TempoSignature::from_bytes(&bad_p256).unwrap_err(),
1442 "Invalid P256 signature length"
1443 );
1444
1445 let mut bad_webauthn = vec![SIGNATURE_TYPE_WEBAUTHN];
1447 bad_webauthn.extend_from_slice(&[0u8; 50]); assert_eq!(
1449 TempoSignature::from_bytes(&bad_webauthn).unwrap_err(),
1450 "Invalid WebAuthn signature length"
1451 );
1452
1453 let mut unknown_type = vec![0xFF];
1455 unknown_type.extend_from_slice(&[0u8; 100]);
1456 assert_eq!(
1457 TempoSignature::from_bytes(&unknown_type).unwrap_err(),
1458 "Unknown signature type identifier"
1459 );
1460 }
1461
1462 #[test]
1463 fn test_tempo_signature_roundtrip() {
1464 use super::{
1465 P256_SIGNATURE_LENGTH, SECP256K1_SIGNATURE_LENGTH, SIGNATURE_TYPE_P256,
1466 SIGNATURE_TYPE_WEBAUTHN,
1467 };
1468
1469 let sig1_bytes = vec![1u8; SECP256K1_SIGNATURE_LENGTH];
1471 let sig1 = TempoSignature::from_bytes(&sig1_bytes).unwrap();
1472 let encoded1 = sig1.to_bytes();
1473 assert_eq!(encoded1.len(), SECP256K1_SIGNATURE_LENGTH); let decoded1 = TempoSignature::from_bytes(&encoded1).unwrap();
1476 assert_eq!(sig1, decoded1);
1477
1478 let mut sig2_bytes = vec![SIGNATURE_TYPE_P256];
1480 sig2_bytes.extend_from_slice(&[2u8; P256_SIGNATURE_LENGTH]);
1481 let sig2 = TempoSignature::from_bytes(&sig2_bytes).unwrap();
1482 let encoded2 = sig2.to_bytes();
1483 assert_eq!(encoded2.len(), 1 + P256_SIGNATURE_LENGTH);
1484 let decoded2 = TempoSignature::from_bytes(&encoded2).unwrap();
1486 assert_eq!(sig2, decoded2);
1487
1488 let mut sig3_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1490 sig3_bytes.extend_from_slice(&[3u8; 200]);
1491 let sig3 = TempoSignature::from_bytes(&sig3_bytes).unwrap();
1492 let encoded3 = sig3.to_bytes();
1493 assert_eq!(encoded3.len(), 1 + 200);
1494 let decoded3 = TempoSignature::from_bytes(&encoded3).unwrap();
1496 assert_eq!(sig3, decoded3);
1497 }
1498
1499 #[test]
1500 #[cfg(feature = "serde")]
1501 fn test_tempo_signature_serde_roundtrip() {
1502 let r_bytes = hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
1506 let s_bytes = hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321");
1507 let sig = Signature::new(
1508 alloy_primitives::U256::from_be_slice(&r_bytes),
1509 alloy_primitives::U256::from_be_slice(&s_bytes),
1510 false,
1511 );
1512 let secp256k1_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig));
1513
1514 let json = serde_json::to_string(&secp256k1_sig).unwrap();
1515 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1516 assert_eq!(secp256k1_sig, decoded, "Secp256k1 serde roundtrip failed");
1517
1518 let p256_sig =
1520 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1521 r: B256::from([1u8; 32]),
1522 s: B256::from([2u8; 32]),
1523 pub_key_x: B256::from([3u8; 32]),
1524 pub_key_y: B256::from([4u8; 32]),
1525 pre_hash: true,
1526 }));
1527
1528 let json = serde_json::to_string(&p256_sig).unwrap();
1529 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1530 assert_eq!(p256_sig, decoded, "P256 serde roundtrip failed");
1531
1532 assert!(
1534 json.contains("\"pubKeyX\""),
1535 "Should use camelCase for pubKeyX"
1536 );
1537 assert!(
1538 json.contains("\"pubKeyY\""),
1539 "Should use camelCase for pubKeyY"
1540 );
1541 assert!(
1542 json.contains("\"preHash\""),
1543 "Should use camelCase for preHash"
1544 );
1545
1546 let webauthn_sig =
1548 TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1549 r: B256::from([5u8; 32]),
1550 s: B256::from([6u8; 32]),
1551 pub_key_x: B256::from([7u8; 32]),
1552 pub_key_y: B256::from([8u8; 32]),
1553 webauthn_data: Bytes::from(vec![9u8; 50]),
1554 }));
1555
1556 let json = serde_json::to_string(&webauthn_sig).unwrap();
1557 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1558 assert_eq!(webauthn_sig, decoded, "WebAuthn serde roundtrip failed");
1559
1560 assert!(
1562 json.contains("\"pubKeyX\""),
1563 "Should use camelCase for pubKeyX"
1564 );
1565 assert!(
1566 json.contains("\"pubKeyY\""),
1567 "Should use camelCase for pubKeyY"
1568 );
1569 assert!(
1570 json.contains("\"webauthnData\""),
1571 "Should use camelCase for webauthnData"
1572 );
1573 }
1574
1575 #[test]
1576 fn test_webauthn_flag_validation() {
1577 let tx_hash = B256::ZERO;
1578
1579 let data = build_webauthn_data(0x41, None, &tx_hash); let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1582 assert!(err.contains("AT flag"), "Should reject AT flag");
1583
1584 let data = build_webauthn_data(0x81, Some(&[0xa0]), &tx_hash); let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1587 assert!(err.contains("ED flag"), "Should reject ED flag");
1588
1589 let data = build_webauthn_data(0x01, None, &tx_hash); assert!(
1592 verify_webauthn_data_internal(&data, &tx_hash).is_ok(),
1593 "Should accept valid webauthn data with only UP flag"
1594 );
1595 }
1596
1597 #[test]
1598 fn test_recover_signer_p256() {
1599 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1600 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1601
1602 let sig_hash = B256::from([0xAA; 32]);
1603 let (r, s) = sign_p256_normalized(&signing_key, &sig_hash);
1604
1605 let p256_sig =
1606 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1607 r,
1608 s,
1609 pub_key_x,
1610 pub_key_y,
1611 pre_hash: false,
1612 }));
1613
1614 let recovered = p256_sig.recover_signer(&sig_hash).unwrap();
1615 assert_eq!(
1616 recovered, expected_address,
1617 "P256 recovery should match derived address"
1618 );
1619 }
1620
1621 #[test]
1622 fn test_recover_signer_p256_with_prehash() {
1623 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1624 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1625
1626 let sig_hash = B256::from([0xBB; 32]);
1628 let prehashed = B256::from_slice(Sha256::digest(sig_hash).as_ref());
1629 let (r, s) = sign_p256_normalized(&signing_key, &prehashed);
1630
1631 let p256_sig =
1632 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1633 r,
1634 s,
1635 pub_key_x,
1636 pub_key_y,
1637 pre_hash: true,
1638 }));
1639
1640 let recovered = p256_sig.recover_signer(&sig_hash).unwrap();
1641 assert_eq!(
1642 recovered, expected_address,
1643 "P256 pre_hash recovery should match"
1644 );
1645 }
1646
1647 #[test]
1648 fn test_recover_signer_p256_high_s_rejected() {
1649 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1650 let sig_hash = B256::from([0xCD; 32]);
1651 let signature: p256::ecdsa::Signature =
1652 signing_key.sign_prehash(sig_hash.as_slice()).unwrap();
1653 let sig_bytes = signature.to_bytes();
1654 let r = B256::from_slice(&sig_bytes[..32]);
1655 let s_value = U256::from_be_slice(&sig_bytes[32..64]);
1656 let high_s = if s_value > P256N_HALF {
1657 s_value
1658 } else {
1659 P256_ORDER - s_value
1660 };
1661
1662 let p256_sig =
1663 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1664 r,
1665 s: B256::from(high_s.to_be_bytes::<32>()),
1666 pub_key_x,
1667 pub_key_y,
1668 pre_hash: false,
1669 }));
1670
1671 assert!(
1672 p256_sig.recover_signer(&sig_hash).is_err(),
1673 "high-s P256 signatures must be rejected"
1674 );
1675 }
1676
1677 #[test]
1678 fn test_recover_signer_webauthn() {
1679 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1680 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1681
1682 let tx_hash = B256::from([0xCC; 32]);
1683 let webauthn_data = build_webauthn_data(0x01, None, &tx_hash);
1684
1685 let message_hash = verify_webauthn_data_internal(&webauthn_data, &tx_hash).unwrap();
1686
1687 let (r, s) = sign_p256_normalized(&signing_key, &message_hash);
1688
1689 let webauthn_sig =
1690 TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1691 r,
1692 s,
1693 pub_key_x,
1694 pub_key_y,
1695 webauthn_data: Bytes::from(webauthn_data),
1696 }));
1697
1698 let recovered = webauthn_sig.recover_signer(&tx_hash).unwrap();
1699 assert_eq!(
1700 recovered, expected_address,
1701 "WebAuthn recovery should match derived address"
1702 );
1703 }
1704
1705 #[test]
1706 fn test_recover_signer_webauthn_invalid_payload_rejected() {
1707 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1708 let tx_hash = B256::from([0xEF; 32]);
1709 let (r, s) = sign_p256_normalized(&signing_key, &B256::ZERO);
1710
1711 let invalid_webauthn_sig =
1712 TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1713 r,
1714 s,
1715 pub_key_x,
1716 pub_key_y,
1717 webauthn_data: Bytes::from(build_webauthn_data(0x41, None, &tx_hash)),
1718 }));
1719
1720 assert!(
1721 invalid_webauthn_sig.recover_signer(&tx_hash).is_err(),
1722 "invalid WebAuthn payloads must be rejected"
1723 );
1724 }
1725
1726 #[test]
1727 fn test_recover_signer_keychain_v1() {
1728 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1729
1730 let (signing_key, access_key_address) = generate_secp256k1_keypair();
1731 let user_address = Address::repeat_byte(0xDD);
1732
1733 let sig_hash = B256::from([0x22; 32]);
1735 let inner_sig = sign_hash(&signing_key, &sig_hash);
1736
1737 let keychain_sig = TempoSignature::Keychain(KeychainSignature::new_v1(
1738 user_address,
1739 match inner_sig {
1740 TempoSignature::Primitive(p) => p,
1741 _ => panic!("Expected primitive signature"),
1742 },
1743 ));
1744
1745 let recovered = keychain_sig.recover_signer(&sig_hash).unwrap();
1747 assert_eq!(
1748 recovered, user_address,
1749 "Keychain V1 recovery should return user_address"
1750 );
1751
1752 let keychain = keychain_sig.as_keychain().unwrap();
1754 let key_id = keychain.key_id(&sig_hash).unwrap();
1755 assert_eq!(
1756 key_id, access_key_address,
1757 "key_id should return access key address"
1758 );
1759
1760 assert!(keychain_sig.is_legacy_keychain());
1762 }
1763
1764 #[test]
1765 fn test_recover_signer_keychain_v2() {
1766 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1767
1768 let (signing_key, access_key_address) = generate_secp256k1_keypair();
1769 let user_address = Address::repeat_byte(0xDD);
1770
1771 let sig_hash = B256::from([0x22; 32]);
1773 let mut buf = [0u8; 53]; buf[0] = SIGNATURE_TYPE_KEYCHAIN_V2;
1775 buf[1..33].copy_from_slice(sig_hash.as_slice());
1776 buf[33..].copy_from_slice(user_address.as_slice());
1777 let effective_hash = keccak256(buf);
1778 let inner_sig = sign_hash(&signing_key, &effective_hash);
1779
1780 let keychain_sig = TempoSignature::Keychain(KeychainSignature::new(
1781 user_address,
1782 match inner_sig {
1783 TempoSignature::Primitive(p) => p,
1784 _ => panic!("Expected primitive signature"),
1785 },
1786 ));
1787
1788 let recovered = keychain_sig.recover_signer(&sig_hash).unwrap();
1790 assert_eq!(
1791 recovered, user_address,
1792 "Keychain V2 recovery should return user_address"
1793 );
1794
1795 let keychain = keychain_sig.as_keychain().unwrap();
1797 let key_id = keychain.key_id(&sig_hash).unwrap();
1798 assert_eq!(
1799 key_id, access_key_address,
1800 "key_id should return access key address"
1801 );
1802
1803 assert!(!keychain_sig.is_legacy_keychain());
1805 }
1806
1807 #[test]
1808 fn test_keychain_v2_binds_user_address() {
1809 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1810
1811 let (signing_key, _access_key_address) = generate_secp256k1_keypair();
1812 let user_a = Address::repeat_byte(0xAA);
1813 let user_b = Address::repeat_byte(0xBB);
1814
1815 let sig_hash = B256::from([0x22; 32]);
1817 let effective_hash = KeychainSignature::signing_hash(sig_hash, user_a);
1818 let inner_sig = sign_hash(&signing_key, &effective_hash);
1819
1820 let inner_primitive = match inner_sig {
1821 TempoSignature::Primitive(p) => p,
1822 _ => panic!("Expected primitive signature"),
1823 };
1824
1825 let sig_a =
1827 TempoSignature::Keychain(KeychainSignature::new(user_a, inner_primitive.clone()));
1828 let recovered_a = sig_a.recover_signer(&sig_hash).unwrap();
1829 assert_eq!(recovered_a, user_a);
1830
1831 let sig_b = TempoSignature::Keychain(KeychainSignature::new(user_b, inner_primitive));
1834 let recovered_b = sig_b.recover_signer(&sig_hash).unwrap();
1835 assert_eq!(
1836 recovered_b, user_b,
1837 "recover_signer returns the claimed user_address"
1838 );
1839
1840 let key_id_a = sig_a.as_keychain().unwrap().key_id(&sig_hash).unwrap();
1842 let key_id_b = sig_b.as_keychain().unwrap().key_id(&sig_hash).unwrap();
1843 assert_ne!(
1844 key_id_a, key_id_b,
1845 "V2 should recover different key_ids for different user_addresses"
1846 );
1847 }
1848
1849 #[test]
1850 fn test_signing_hash_properties() {
1851 let hash_a = B256::from([0x11; 32]);
1852 let hash_b = B256::from([0x22; 32]);
1853 let addr_a = Address::repeat_byte(0xAA);
1854 let addr_b = Address::repeat_byte(0xBB);
1855
1856 assert_ne!(
1858 KeychainSignature::signing_hash(hash_a, addr_a),
1859 KeychainSignature::signing_hash(hash_a, addr_b),
1860 );
1861
1862 assert_ne!(
1864 KeychainSignature::signing_hash(hash_a, addr_a),
1865 KeychainSignature::signing_hash(hash_b, addr_a),
1866 );
1867
1868 assert_eq!(
1870 KeychainSignature::signing_hash(hash_a, addr_a),
1871 KeychainSignature::signing_hash(hash_a, addr_a),
1872 );
1873 }
1874
1875 #[test]
1876 fn test_webauthn_rejects_challenge_injection() {
1877 let (tx_hash, attack_hash) = (B256::from([0xAA; 32]), B256::from([0xFF; 32]));
1878 let (challenge, attack_challenge) = (
1879 URL_SAFE_NO_PAD.encode(tx_hash.as_slice()),
1880 URL_SAFE_NO_PAD.encode(attack_hash.as_slice()),
1881 );
1882
1883 let valid_payload = format!(r#"{{"type":"webauthn.get","challenge":"{challenge}"}}"#);
1885
1886 let mut auth_data = vec![0u8; 37];
1887 auth_data[32] = 0x01;
1888 let mut webauthn_data = auth_data;
1889 webauthn_data.extend_from_slice(valid_payload.as_bytes());
1890
1891 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1892 assert!(result.is_ok());
1893
1894 let attack_variants = [
1896 format!(
1897 r#"{{"type":"webauthn.get","challenge":"{attack_challenge}","extra":{{"challenge":"{challenge}"}}}}"#
1898 ),
1899 format!(
1900 r#"{{"type":"webauthn.get","data":[{{"challenge":"{challenge}"}}],"challenge":"{attack_challenge}"}}"#
1901 ),
1902 ];
1903
1904 for (i, attack_json) in attack_variants.iter().enumerate() {
1905 let mut auth_data = vec![0u8; 37];
1906 auth_data[32] = 0x01;
1907 let mut webauthn_data = auth_data;
1908 webauthn_data.extend_from_slice(attack_json.as_bytes());
1909
1910 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1911 assert!(
1912 result.is_err(),
1913 "Attack variant {i} should be rejected: {attack_json}"
1914 );
1915 }
1916 }
1917
1918 #[test]
1919 fn test_keychain_signature_eq_same() {
1920 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1921 let addr = Address::repeat_byte(0x01);
1922 let a = KeychainSignature::new(addr, sig.clone());
1923 let b = KeychainSignature::new(addr, sig);
1924 assert_eq!(a, b);
1925 }
1926
1927 #[test]
1928 fn test_keychain_signature_eq_different_address() {
1929 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1930 let a = KeychainSignature::new(Address::repeat_byte(0x01), sig.clone());
1931 let b = KeychainSignature::new(Address::repeat_byte(0x02), sig);
1932 assert_ne!(a, b);
1933 }
1934
1935 #[test]
1936 fn test_keychain_signature_eq_different_signature() {
1937 let addr = Address::repeat_byte(0x01);
1938 let sig_a = PrimitiveSignature::Secp256k1(Signature::test_signature());
1939 let sig_b = PrimitiveSignature::P256(P256SignatureWithPreHash {
1940 r: B256::from([1u8; 32]),
1941 s: B256::from([2u8; 32]),
1942 pub_key_x: B256::from([3u8; 32]),
1943 pub_key_y: B256::from([4u8; 32]),
1944 pre_hash: false,
1945 });
1946 let a = KeychainSignature::new(addr, sig_a);
1947 let b = KeychainSignature::new(addr, sig_b);
1948 assert_ne!(a, b);
1949 }
1950
1951 #[test]
1952 fn test_keychain_signature_hash_differs_for_different_sigs() {
1953 use std::{
1954 collections::hash_map::DefaultHasher,
1955 hash::{Hash, Hasher},
1956 };
1957
1958 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1959 let a = KeychainSignature::new(Address::repeat_byte(0x01), sig.clone());
1960 let b = KeychainSignature::new(Address::repeat_byte(0x02), sig.clone());
1961 let c = KeychainSignature::new(Address::repeat_byte(0x01), sig);
1962
1963 let hash = |k: &KeychainSignature| {
1964 let mut h = DefaultHasher::new();
1965 k.hash(&mut h);
1966 h.finish()
1967 };
1968
1969 assert_ne!(
1970 hash(&a),
1971 hash(&b),
1972 "different address should produce different hash"
1973 );
1974 assert_eq!(hash(&a), hash(&c), "same fields should produce same hash");
1975 }
1976
1977 #[test]
1978 fn test_primitive_signature_from_bytes_one_byte() {
1979 let result = PrimitiveSignature::from_bytes(&[0x01]);
1980 assert!(result.is_err());
1981 assert!(result.unwrap_err().contains("too short"));
1982 }
1983
1984 #[test]
1985 fn test_tempo_signature_keychain_too_short_for_address() {
1986 for type_byte in [SIGNATURE_TYPE_KEYCHAIN, SIGNATURE_TYPE_KEYCHAIN_V2] {
1987 let mut data = vec![type_byte];
1988 data.extend_from_slice(&[0u8; 19]);
1989 let result = TempoSignature::from_bytes(&data);
1990 assert!(result.is_err());
1991 assert!(result.unwrap_err().contains("too short"));
1992 }
1993 }
1994
1995 #[test]
1996 fn test_tempo_signature_keychain_exactly_20_bytes_inner_empty() {
1997 let mut data = vec![SIGNATURE_TYPE_KEYCHAIN];
1998 data.extend_from_slice(&[0u8; 20]);
1999 let result = TempoSignature::from_bytes(&data);
2000 assert!(result.is_err());
2001 }
2002
2003 #[test]
2004 fn test_is_keychain_returns_false_for_primitive() {
2005 let sig =
2006 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
2007 assert!(!sig.is_keychain());
2008 }
2009
2010 #[test]
2011 fn test_is_keychain_returns_true_for_keychain() {
2012 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2013 let sig = TempoSignature::Keychain(KeychainSignature::new(Address::ZERO, inner));
2014 assert!(sig.is_keychain());
2015 }
2016
2017 #[test]
2018 fn test_keychain_v1_v2_bytes_roundtrip_and_wire_format() {
2019 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2020 let user = Address::repeat_byte(0xAA);
2021
2022 let v1 = TempoSignature::Keychain(KeychainSignature::new_v1(user, inner.clone()));
2024 let v1_bytes = v1.to_bytes();
2025 assert_eq!(v1_bytes[0], SIGNATURE_TYPE_KEYCHAIN);
2026 let v1_decoded = TempoSignature::from_bytes(&v1_bytes).unwrap();
2027 assert_eq!(v1, v1_decoded);
2028 assert!(v1_decoded.is_legacy_keychain());
2029
2030 let v2 = TempoSignature::Keychain(KeychainSignature::new(user, inner));
2032 let v2_bytes = v2.to_bytes();
2033 assert_eq!(v2_bytes[0], SIGNATURE_TYPE_KEYCHAIN_V2);
2034 let v2_decoded = TempoSignature::from_bytes(&v2_bytes).unwrap();
2035 assert_eq!(v2, v2_decoded);
2036 assert!(!v2_decoded.is_legacy_keychain());
2037
2038 assert_ne!(v1, v2);
2040 }
2041
2042 #[test]
2043 #[cfg(feature = "serde")]
2044 fn test_keychain_serde_roundtrip_and_backward_compat() {
2045 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2046 let user = Address::repeat_byte(0xBB);
2047
2048 let v2 = TempoSignature::Keychain(KeychainSignature::new(user, inner.clone()));
2050 let json = serde_json::to_string(&v2).unwrap();
2051 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
2052 assert_eq!(v2, decoded);
2053 assert!(!decoded.is_legacy_keychain());
2054
2055 let v1 = TempoSignature::Keychain(KeychainSignature::new_v1(user, inner));
2057 let json_v1 = serde_json::to_string(&v1).unwrap();
2058 let decoded_v1: TempoSignature = serde_json::from_str(&json_v1).unwrap();
2059 assert_eq!(v1, decoded_v1);
2060 assert!(decoded_v1.is_legacy_keychain());
2061
2062 let json_no_version = json_v1.replace(r#","version":"v1""#, "");
2064 assert!(
2065 !json_no_version.contains("version"),
2066 "version field should be stripped"
2067 );
2068 let decoded_no_version: TempoSignature = serde_json::from_str(&json_no_version).unwrap();
2069 assert!(decoded_no_version.is_legacy_keychain());
2070 }
2071
2072 #[test]
2073 fn test_keychain_rlp_roundtrip_preserves_version() {
2074 use alloy_rlp::Decodable;
2075
2076 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2077 let user = Address::repeat_byte(0xCC);
2078
2079 for (sig, expect_legacy) in [
2080 (
2081 TempoSignature::Keychain(KeychainSignature::new_v1(user, inner.clone())),
2082 true,
2083 ),
2084 (
2085 TempoSignature::Keychain(KeychainSignature::new(user, inner)),
2086 false,
2087 ),
2088 ] {
2089 let mut buf = Vec::new();
2090 alloy_rlp::Encodable::encode(&sig, &mut buf);
2091 let decoded = TempoSignature::decode(&mut buf.as_slice()).unwrap();
2092 assert_eq!(sig, decoded);
2093 assert_eq!(decoded.is_legacy_keychain(), expect_legacy);
2094 }
2095 }
2096}
2097
2098#[cfg(all(test, feature = "reth-codec"))]
2099mod compact_tests {
2100 use super::*;
2101 use alloy_primitives::{b256, bytes, hex};
2102 use reth_codecs::Compact;
2103
2104 #[test]
2109 fn compact_types_have_unused_bits() {
2110 assert_ne!(
2111 P256SignatureWithPreHash::bitflag_unused_bits(),
2112 0,
2113 "P256SignatureWithPreHash"
2114 );
2115 }
2116
2117 #[test]
2118 fn p256_signature_compact_roundtrip() {
2119 let sig = P256SignatureWithPreHash {
2120 r: b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
2121 s: b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
2122 pub_key_x: b256!("0x3333333333333333333333333333333333333333333333333333333333333333"),
2123 pub_key_y: b256!("0x4444444444444444444444444444444444444444444444444444444444444444"),
2124 pre_hash: true,
2125 };
2126
2127 let expected = hex!(
2128 "011111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444"
2129 );
2130
2131 let mut buf = vec![];
2132 let len = sig.to_compact(&mut buf);
2133 assert_eq!(
2134 buf, expected,
2135 "P256SignatureWithPreHash compact encoding changed"
2136 );
2137 assert_eq!(len, expected.len());
2138
2139 let (decoded, _) = P256SignatureWithPreHash::from_compact(&expected, expected.len());
2140 assert_eq!(decoded, sig);
2141 }
2142
2143 #[test]
2144 fn webauthn_signature_compact_roundtrip() {
2145 let sig = WebAuthnSignature {
2146 r: b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
2147 s: b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
2148 pub_key_x: b256!("0x3333333333333333333333333333333333333333333333333333333333333333"),
2149 pub_key_y: b256!("0x4444444444444444444444444444444444444444444444444444444444444444"),
2150 webauthn_data: bytes!("aabbccdd"),
2151 };
2152
2153 let expected = hex!(
2154 "1111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444aabbccdd"
2155 );
2156
2157 let mut buf = vec![];
2158 let len = sig.to_compact(&mut buf);
2159 assert_eq!(buf, expected, "WebAuthnSignature compact encoding changed");
2160 assert_eq!(len, expected.len());
2161
2162 let (decoded, _) = WebAuthnSignature::from_compact(&expected, expected.len());
2163 assert_eq!(decoded, sig);
2164 }
2165}