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 self.to_bytes().length()
331 }
332}
333
334impl alloy_rlp::Decodable for PrimitiveSignature {
335 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
336 let bytes: Bytes = alloy_rlp::Decodable::decode(buf)?;
337 Self::from_bytes(&bytes).map_err(alloy_rlp::Error::Custom)
338 }
339}
340
341#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
345#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
346#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
347#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
348pub enum KeychainVersion {
349 #[default]
353 V1,
354 V2,
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub enum KeychainVersionError {
365 LegacyPostT1C,
367 V2BeforeActivation,
369}
370
371#[derive(Clone, Debug)]
385#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
386#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
387pub struct KeychainSignature {
388 pub user_address: Address,
390 pub signature: PrimitiveSignature,
392 #[cfg_attr(feature = "serde", serde(default))]
394 pub version: KeychainVersion,
395 #[cfg_attr(
400 feature = "serde",
401 serde(
402 serialize_with = "serialize_once_lock",
403 rename = "keyId",
404 skip_deserializing,
405 )
406 )]
407 cached_key_id: OnceLock<Address>,
408}
409
410impl KeychainSignature {
411 pub fn new(user_address: Address, signature: PrimitiveSignature) -> Self {
415 Self {
416 user_address,
417 signature,
418 version: KeychainVersion::V2,
419 cached_key_id: OnceLock::new(),
420 }
421 }
422
423 pub fn new_v1(user_address: Address, signature: PrimitiveSignature) -> Self {
428 Self {
429 user_address,
430 signature,
431 version: KeychainVersion::V1,
432 cached_key_id: OnceLock::new(),
433 }
434 }
435
436 fn effective_sig_hash(&self, sig_hash: &B256) -> B256 {
441 match self.version {
442 KeychainVersion::V1 => *sig_hash,
443 KeychainVersion::V2 => Self::signing_hash(*sig_hash, self.user_address),
444 }
445 }
446
447 pub fn key_id(
455 &self,
456 sig_hash: &B256,
457 ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
458 if let Some(cached) = self.cached_key_id.get() {
460 return Ok(*cached);
461 }
462
463 let effective_hash = self.effective_sig_hash(sig_hash);
465 let key_id = self.signature.recover_signer(&effective_hash)?;
466 #[allow(clippy::useless_conversion)]
467 let _ = self.cached_key_id.set(key_id.into());
468 Ok(key_id)
469 }
470
471 pub fn is_legacy(&self) -> bool {
473 self.version == KeychainVersion::V1
474 }
475
476 pub fn signing_hash(sig_hash: B256, user_address: Address) -> B256 {
483 let mut buf = [0u8; 53]; buf[0] = SIGNATURE_TYPE_KEYCHAIN_V2;
485 buf[1..33].copy_from_slice(sig_hash.as_slice());
486 buf[33..].copy_from_slice(user_address.as_slice());
487 keccak256(buf)
488 }
489}
490
491impl PartialEq for KeychainSignature {
494 fn eq(&self, other: &Self) -> bool {
495 self.user_address == other.user_address
496 && self.signature == other.signature
497 && self.version == other.version
498 }
499}
500
501impl Eq for KeychainSignature {}
502
503impl core::hash::Hash for KeychainSignature {
504 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
505 self.user_address.hash(state);
506 self.signature.hash(state);
507 self.version.hash(state);
508 }
509}
510
511#[cfg(any(test, feature = "arbitrary"))]
513impl<'a> arbitrary::Arbitrary<'a> for KeychainSignature {
514 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
515 Ok(Self {
516 user_address: u.arbitrary()?,
517 signature: u.arbitrary()?,
518 version: u.arbitrary()?,
519 cached_key_id: OnceLock::new(), })
521 }
522}
523
524#[derive(Clone, Debug, PartialEq, Eq, Hash)]
528#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
529#[cfg_attr(feature = "serde", serde(untagged, rename_all = "camelCase"))]
530#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
531#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
532pub enum TempoSignature {
533 Primitive(PrimitiveSignature),
535
536 Keychain(KeychainSignature),
541}
542
543impl TempoSignature {
544 pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
550 if data.is_empty() {
551 return Err("Signature data is empty");
552 }
553
554 if data.len() > 1
557 && data.len() != SECP256K1_SIGNATURE_LENGTH
558 && (data[0] == SIGNATURE_TYPE_KEYCHAIN || data[0] == SIGNATURE_TYPE_KEYCHAIN_V2)
559 {
560 let version = if data[0] == SIGNATURE_TYPE_KEYCHAIN {
561 KeychainVersion::V1
562 } else {
563 KeychainVersion::V2
564 };
565 let sig_data = &data[1..];
566
567 if sig_data.len() < 20 {
569 return Err("Invalid Keychain signature: too short for user_address");
570 }
571
572 let user_address = Address::from_slice(&sig_data[0..20]);
573 let inner_sig_bytes = &sig_data[20..];
574
575 let inner_signature = PrimitiveSignature::from_bytes(inner_sig_bytes)?;
578
579 return Ok(Self::Keychain(KeychainSignature {
580 user_address,
581 signature: inner_signature,
582 version,
583 cached_key_id: OnceLock::new(),
584 }));
585 }
586
587 let primitive = PrimitiveSignature::from_bytes(data)?;
589 Ok(Self::Primitive(primitive))
590 }
591
592 pub fn to_bytes(&self) -> Bytes {
598 match self {
599 Self::Primitive(primitive_sig) => primitive_sig.to_bytes(),
600 Self::Keychain(keychain_sig) => {
601 let inner_bytes = keychain_sig.signature.to_bytes();
603 let mut bytes = Vec::with_capacity(1 + 20 + inner_bytes.len());
604 let type_byte = match keychain_sig.version {
605 KeychainVersion::V1 => SIGNATURE_TYPE_KEYCHAIN,
606 KeychainVersion::V2 => SIGNATURE_TYPE_KEYCHAIN_V2,
607 };
608 bytes.push(type_byte);
609 bytes.extend_from_slice(keychain_sig.user_address.as_slice());
610 bytes.extend_from_slice(&inner_bytes);
611 Bytes::from(bytes)
612 }
613 }
614 }
615
616 pub fn encoded_length(&self) -> usize {
622 match self {
623 Self::Primitive(primitive_sig) => primitive_sig.encoded_length(),
624 Self::Keychain(keychain_sig) => 1 + 20 + keychain_sig.signature.encoded_length(),
625 }
626 }
627
628 pub fn signature_type(&self) -> SignatureType {
630 match self {
631 Self::Primitive(primitive_sig) => primitive_sig.signature_type(),
632 Self::Keychain(keychain_sig) => keychain_sig.signature.signature_type(),
633 }
634 }
635
636 pub fn size(&self) -> usize {
638 match self {
639 Self::Primitive(primitive_sig) => primitive_sig.size(),
640 Self::Keychain(keychain_sig) => 1 + 20 + keychain_sig.signature.size(),
641 }
642 }
643
644 pub fn recover_signer(
659 &self,
660 sig_hash: &B256,
661 ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
662 match self {
663 Self::Primitive(primitive_sig) => primitive_sig.recover_signer(sig_hash),
664 Self::Keychain(keychain_sig) => {
665 keychain_sig.key_id(sig_hash)?;
667
668 Ok(keychain_sig.user_address)
670 }
671 }
672 }
673
674 pub fn is_keychain(&self) -> bool {
676 matches!(self, Self::Keychain(_))
677 }
678
679 pub fn is_legacy_keychain(&self) -> bool {
681 matches!(self, Self::Keychain(k) if k.is_legacy())
682 }
683
684 pub fn is_v2_keychain(&self) -> bool {
686 matches!(
687 self,
688 Self::Keychain(KeychainSignature {
689 version: KeychainVersion::V2,
690 ..
691 })
692 )
693 }
694
695 pub fn validate_version(&self, is_t1c: bool) -> Result<(), KeychainVersionError> {
700 if is_t1c && self.is_legacy_keychain() {
701 return Err(KeychainVersionError::LegacyPostT1C);
702 }
703 if !is_t1c && self.is_v2_keychain() {
704 return Err(KeychainVersionError::V2BeforeActivation);
705 }
706 Ok(())
707 }
708
709 pub fn as_keychain(&self) -> Option<&KeychainSignature> {
711 match self {
712 Self::Keychain(keychain_sig) => Some(keychain_sig),
713 _ => None,
714 }
715 }
716}
717
718impl Default for TempoSignature {
719 fn default() -> Self {
720 Self::Primitive(PrimitiveSignature::default())
721 }
722}
723
724impl alloy_rlp::Encodable for TempoSignature {
725 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
726 let bytes = self.to_bytes();
727 alloy_rlp::Encodable::encode(&bytes, out);
728 }
729
730 fn length(&self) -> usize {
731 self.to_bytes().length()
732 }
733}
734
735impl alloy_rlp::Decodable for TempoSignature {
736 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
737 let bytes: Bytes = alloy_rlp::Decodable::decode(buf)?;
738 Self::from_bytes(&bytes).map_err(alloy_rlp::Error::Custom)
739 }
740}
741
742impl From<Signature> for TempoSignature {
743 fn from(signature: Signature) -> Self {
744 Self::Primitive(PrimitiveSignature::Secp256k1(signature))
745 }
746}
747
748pub fn derive_p256_address(pub_key_x: &B256, pub_key_y: &B256) -> Address {
754 let hash = keccak256([pub_key_x.as_slice(), pub_key_y.as_slice()].concat());
755
756 Address::from_slice(&hash[12..])
758}
759
760fn concat<const N: usize>(slices: &[&[u8]]) -> [u8; N] {
762 let mut out = [0u8; N];
763 let mut offset = 0;
764 for s in slices {
765 out[offset..offset + s.len()].copy_from_slice(s);
766 offset += s.len();
767 }
768 debug_assert_eq!(offset, N, "slices length doesn't match array size");
769 out
770}
771
772#[cfg(feature = "std")]
773fn verify_p256_signature_with_aws_lc(
774 r: &[u8],
775 s: &[u8],
776 pub_key_x: &[u8],
777 pub_key_y: &[u8],
778 message_hash: &B256,
779) -> Result<(), &'static str> {
780 use aws_lc_rs::{
781 digest::{Digest as AwsLcDigest, SHA256 as AwsLcSha256},
782 signature::{ECDSA_P256_SHA256_FIXED, ParsedPublicKey as AwsLcParsedPublicKey},
783 };
784
785 let encoded_point = concat::<65>(&[&[0x04], pub_key_x, pub_key_y]);
786 let verifying_key = AwsLcParsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, encoded_point)
787 .map_err(|_| "Invalid P256 public key")?;
788
789 let signature = concat::<64>(&[r, s]);
790
791 let digest = AwsLcDigest::import_less_safe(message_hash.as_slice(), &AwsLcSha256)
794 .map_err(|_| "Invalid P256 message digest")?;
795
796 verifying_key
797 .verify_digest_sig(&digest, &signature)
798 .map_err(|_| "P256 signature verification failed")
799}
800
801#[cfg(any(test, not(feature = "std")))]
802fn verify_p256_signature_with_p256(
803 r: &[u8],
804 s: &[u8],
805 pub_key_x: &[u8],
806 pub_key_y: &[u8],
807 message_hash: &B256,
808) -> Result<(), &'static str> {
809 use p256::{
810 EncodedPoint,
811 ecdsa::{Signature as P256Signature, VerifyingKey, signature::hazmat::PrehashVerifier},
812 };
813
814 let encoded_point =
815 EncodedPoint::from_affine_coordinates(pub_key_x.into(), pub_key_y.into(), false);
816 let verifying_key =
817 VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| "Invalid P256 public key")?;
818
819 let signature = P256Signature::from_slice(&concat::<64>(&[r, s]))
820 .map_err(|_| "Invalid P256 signature encoding")?;
821
822 verifying_key
824 .verify_prehash(message_hash.as_slice(), &signature)
825 .map_err(|_| "P256 signature verification failed")
826}
827
828fn verify_p256_signature_internal(
844 r: &[u8],
845 s: &[u8],
846 pub_key_x: &[u8],
847 pub_key_y: &[u8],
848 message_hash: &B256,
849) -> Result<(), &'static str> {
850 if U256::from_be_slice(s) > P256N_HALF {
852 return Err("P256 signature has high s value");
853 }
854
855 #[cfg(all(feature = "std", not(test)))]
856 {
857 verify_p256_signature_with_aws_lc(r, s, pub_key_x, pub_key_y, message_hash)
859 }
860
861 #[cfg(not(feature = "std"))]
862 {
863 verify_p256_signature_with_p256(r, s, pub_key_x, pub_key_y, message_hash)
865 }
866
867 #[cfg(all(feature = "std", test))]
868 {
869 let aws_lc = verify_p256_signature_with_aws_lc(r, s, pub_key_x, pub_key_y, message_hash);
871 let p256 = verify_p256_signature_with_p256(r, s, pub_key_x, pub_key_y, message_hash);
872
873 assert_eq!(
874 aws_lc.is_ok(),
875 p256.is_ok(),
876 "aws-lc and p256 verification backends disagreed"
877 );
878
879 aws_lc
880 }
881}
882
883#[derive(serde::Deserialize)]
886struct ClientDataJson<'a> {
887 #[serde(rename = "type")]
888 type_field: &'a str,
889 challenge: &'a str,
890}
891
892fn verify_webauthn_data_internal(
900 webauthn_data: &[u8],
901 tx_hash: &B256,
902) -> Result<B256, &'static str> {
903 if webauthn_data.len() < MIN_AUTH_DATA_LEN + 32 {
905 return Err("WebAuthn data too short");
906 }
907
908 let flags = webauthn_data[32];
910 let (up_flag, uv_flag, at_flag, ed_flag) = (flags & UP, flags & UV, flags & AT, flags & ED);
911
912 if up_flag == 0 && uv_flag == 0 {
914 return Err("neither UP, nor UV flag set");
915 }
916
917 if at_flag != 0 {
919 return Err("AT flag must not be set for assertion signatures");
920 }
921
922 let auth_data_len = if ed_flag == 0 {
924 MIN_AUTH_DATA_LEN
926 } else {
927 return Err("ED flag must not be set, as Tempo doesn't support extensions");
930 };
931
932 let authenticator_data = &webauthn_data[..auth_data_len];
933 let client_data_json = &webauthn_data[auth_data_len..];
934
935 let client_data: ClientDataJson<'_> =
938 serde_json::from_slice(client_data_json).map_err(|_| "clientDataJSON is not valid JSON")?;
939
940 if client_data.type_field != "webauthn.get" {
942 return Err("clientDataJSON type must be webauthn.get");
943 }
944
945 if client_data.challenge != URL_SAFE_NO_PAD.encode(tx_hash.as_slice()) {
947 return Err("clientDataJSON challenge does not match transaction hash");
948 }
949
950 let client_data_hash = Sha256::digest(client_data_json);
953
954 let mut final_hasher = Sha256::new();
955 final_hasher.update(authenticator_data);
956 final_hasher.update(client_data_hash);
957 let message_hash = final_hasher.finalize();
958
959 Ok(B256::from_slice(&message_hash))
960}
961
962#[cfg(feature = "serde")]
963fn serialize_once_lock<S>(value: &OnceLock<Address>, serializer: S) -> Result<S::Ok, S::Error>
965where
966 S: serde::Serializer,
967{
968 serde::Serialize::serialize(&value.get(), serializer)
969}
970
971#[cfg(test)]
972mod tests {
973 use super::*;
974 use alloy_primitives::hex;
975 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
976 use p256::{
977 ecdsa::{SigningKey as P256SigningKey, signature::hazmat::PrehashSigner},
978 elliptic_curve::rand_core::OsRng,
979 };
980
981 fn generate_p256_keypair() -> (P256SigningKey, B256, B256) {
983 let signing_key = P256SigningKey::random(&mut OsRng);
984 let verifying_key = signing_key.verifying_key();
985 let encoded_point = verifying_key.to_encoded_point(false);
986 let pub_key_x = B256::from_slice(encoded_point.x().unwrap().as_ref());
987 let pub_key_y = B256::from_slice(encoded_point.y().unwrap().as_ref());
988 (signing_key, pub_key_x, pub_key_y)
989 }
990
991 fn sign_p256_normalized(signing_key: &P256SigningKey, message_hash: &B256) -> (B256, B256) {
993 let signature: p256::ecdsa::Signature =
994 signing_key.sign_prehash(message_hash.as_slice()).unwrap();
995 let sig_bytes = signature.to_bytes();
996 let r = B256::from_slice(&sig_bytes[0..32]);
997 let s = normalize_p256_s(&sig_bytes[32..64]).expect("p256 crate produces valid s");
998 (r, s)
999 }
1000
1001 fn build_webauthn_data(flags: u8, extension: Option<&[u8]>, tx_hash: &B256) -> Vec<u8> {
1003 let mut data = vec![0u8; 32]; data.push(flags);
1005 data.extend_from_slice(&[0u8; 4]); if let Some(ext) = extension {
1007 data.extend_from_slice(ext);
1008 }
1009 let challenge = URL_SAFE_NO_PAD.encode(tx_hash.as_slice());
1010 data.extend_from_slice(
1011 format!("{{\"type\":\"webauthn.get\",\"challenge\":\"{challenge}\"}}").as_bytes(),
1012 );
1013 data
1014 }
1015
1016 #[test]
1017 fn test_p256_high_s_normalization() {
1018 let low_s = U256::from(1u64);
1020 let low_s_bytes: [u8; 32] = low_s.to_be_bytes();
1021 assert_eq!(
1022 U256::from_be_slice(normalize_p256_s(&low_s_bytes).unwrap().as_slice()),
1023 low_s,
1024 "s < P256N_HALF should remain unchanged"
1025 );
1026
1027 let half_bytes: [u8; 32] = P256N_HALF.to_be_bytes();
1029 assert_eq!(
1030 U256::from_be_slice(normalize_p256_s(&half_bytes).unwrap().as_slice()),
1031 P256N_HALF,
1032 "s == P256N_HALF should remain unchanged"
1033 );
1034
1035 let high_s = P256N_HALF + U256::from(1u64);
1037 let high_s_bytes: [u8; 32] = high_s.to_be_bytes();
1038 assert_eq!(
1039 U256::from_be_slice(normalize_p256_s(&high_s_bytes).unwrap().as_slice()),
1040 P256_ORDER - high_s,
1041 "s > P256N_HALF should be normalized"
1042 );
1043
1044 let max_s = P256_ORDER - U256::from(1u64);
1046 let max_s_bytes: [u8; 32] = max_s.to_be_bytes();
1047 assert_eq!(
1048 U256::from_be_slice(normalize_p256_s(&max_s_bytes).unwrap().as_slice()),
1049 U256::from(1u64),
1050 "s == P256_ORDER - 1 should normalize to 1"
1051 );
1052
1053 let zero_bytes: [u8; 32] = U256::ZERO.to_be_bytes();
1055 assert!(
1056 normalize_p256_s(&zero_bytes).is_err(),
1057 "s == 0 should be rejected"
1058 );
1059
1060 let order_bytes: [u8; 32] = P256_ORDER.to_be_bytes();
1062 assert!(
1063 normalize_p256_s(&order_bytes).is_err(),
1064 "s == P256_ORDER should be rejected"
1065 );
1066
1067 let over_bytes: [u8; 32] = (P256_ORDER + U256::from(1u64)).to_be_bytes();
1069 assert!(
1070 normalize_p256_s(&over_bytes).is_err(),
1071 "s > P256_ORDER should be rejected"
1072 );
1073
1074 let max_bytes: [u8; 32] = U256::MAX.to_be_bytes();
1076 assert!(
1077 normalize_p256_s(&max_bytes).is_err(),
1078 "s == U256::MAX should be rejected"
1079 );
1080 }
1081
1082 #[test]
1083 fn test_p256_signature_verification_invalid_pubkey() {
1084 let r = [0u8; 32];
1086 let s = [0u8; 32];
1087 let pub_key_x = [0u8; 32]; let pub_key_y = [0u8; 32];
1089 let message_hash = B256::ZERO;
1090
1091 let result = verify_p256_signature_internal(&r, &s, &pub_key_x, &pub_key_y, &message_hash);
1092 assert!(result.is_err());
1093 }
1094
1095 #[test]
1096 fn test_p256_signature_verification_invalid_signature() {
1097 let (_, pub_key_x, pub_key_y) = generate_p256_keypair();
1098 let message_hash = B256::ZERO;
1099
1100 let assert_invalid = |r: &[u8], s: &[u8], context: &str| {
1101 let result = verify_p256_signature_internal(
1102 r,
1103 s,
1104 pub_key_x.as_slice(),
1105 pub_key_y.as_slice(),
1106 &message_hash,
1107 );
1108 assert!(result.is_err(), "{context} should fail verification");
1109 };
1110
1111 let r = [0u8; 32];
1113 let s = [0u8; 32];
1114 assert_invalid(&r, &s, "all-zero signature");
1115
1116 let one = U256::from(1u64).to_be_bytes::<32>();
1117 let order = P256_ORDER.to_be_bytes::<32>();
1118 assert_invalid(&order, &one, "signature with r == P256_ORDER");
1119 assert_invalid(&one, &order, "signature with s == P256_ORDER");
1120 }
1121
1122 #[test]
1123 fn test_p256_signature_verification_valid() {
1124 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1125 let message_hash = B256::from_slice(&Sha256::digest(b"test message"));
1126 let (r, s) = sign_p256_normalized(&signing_key, &message_hash);
1127
1128 let result = verify_p256_signature_internal(
1129 r.as_slice(),
1130 s.as_slice(),
1131 pub_key_x.as_slice(),
1132 pub_key_y.as_slice(),
1133 &message_hash,
1134 );
1135 assert!(
1136 result.is_ok(),
1137 "Valid P256 signature should verify successfully"
1138 );
1139 }
1140
1141 #[test]
1142 fn test_p256_high_s_rejection() {
1143 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1144 let message_hash = B256::from_slice(&Sha256::digest(b"test message for high s"));
1145
1146 let signature: p256::ecdsa::Signature =
1148 signing_key.sign_prehash(message_hash.as_slice()).unwrap();
1149 let sig_bytes = signature.to_bytes();
1150 let r = &sig_bytes[0..32];
1151 let original_s = &sig_bytes[32..64];
1152
1153 let s_value = alloy_primitives::U256::from_be_slice(original_s);
1155 let computed_high_s = P256_ORDER - s_value;
1156 let computed_high_s_bytes: [u8; 32] = computed_high_s.to_be_bytes();
1157
1158 let s_is_low = s_value <= P256N_HALF;
1161 if s_is_low {
1162 let result = verify_p256_signature_internal(
1164 r,
1165 &computed_high_s_bytes,
1166 pub_key_x.as_slice(),
1167 pub_key_y.as_slice(),
1168 &message_hash,
1169 );
1170 assert!(
1171 result.is_err(),
1172 "High-s signature should be rejected for signature malleability prevention"
1173 );
1174 assert_eq!(result.unwrap_err(), "P256 signature has high s value");
1175 } else {
1176 let original_result = verify_p256_signature_internal(
1179 r,
1180 original_s,
1181 pub_key_x.as_slice(),
1182 pub_key_y.as_slice(),
1183 &message_hash,
1184 );
1185 assert!(
1186 original_result.is_err(),
1187 "Original high-s signature should be rejected"
1188 );
1189 }
1190 }
1191
1192 #[test]
1193 fn test_webauthn_data_verification_too_short() {
1194 let short_data = vec![0u8; 36];
1196 let tx_hash = B256::ZERO;
1197
1198 let result = verify_webauthn_data_internal(&short_data, &tx_hash);
1199 assert!(result.is_err());
1200 assert_eq!(result.unwrap_err(), "WebAuthn data too short");
1201 }
1202
1203 #[test]
1204 fn test_webauthn_data_verification_missing_up_and_uv_flags() {
1205 let tx_hash = B256::ZERO;
1206 let client_data = b"{\"type\":\"webauthn.get\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
1207
1208 let mut auth_data = vec![0u8; 37];
1210 auth_data[32] = 0x00;
1211 let mut webauthn_data = auth_data;
1212 webauthn_data.extend_from_slice(client_data);
1213
1214 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1215 assert!(result.is_err());
1216 assert_eq!(result.unwrap_err(), "neither UP, nor UV flag set");
1217
1218 let mut auth_data = vec![0u8; 37];
1220 auth_data[32] = 0x04;
1221 let mut webauthn_data = auth_data;
1222 webauthn_data.extend_from_slice(client_data);
1223
1224 assert!(verify_webauthn_data_internal(&webauthn_data, &tx_hash).is_ok());
1225 }
1226
1227 #[test]
1228 fn test_webauthn_data_verification_invalid_type() {
1229 let mut auth_data = vec![0u8; 37];
1231 auth_data[32] = 0x01; let client_data = b"{\"type\":\"webauthn.create\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
1235 let mut webauthn_data = auth_data;
1236 webauthn_data.extend_from_slice(client_data);
1237
1238 let tx_hash = B256::ZERO;
1239 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1240
1241 assert!(result.is_err());
1242 assert_eq!(
1243 result.unwrap_err(),
1244 "clientDataJSON type must be webauthn.get"
1245 );
1246 }
1247
1248 #[test]
1249 fn test_webauthn_data_verification_invalid_challenge() {
1250 let mut auth_data = vec![0u8; 37];
1252 auth_data[32] = 0x01; let client_data =
1256 b"{\"type\":\"webauthn.get\",\"challenge\":\"wrong_challenge_value_here\"}";
1257 let mut webauthn_data = auth_data;
1258 webauthn_data.extend_from_slice(client_data);
1259
1260 let tx_hash = B256::ZERO;
1261 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1262
1263 assert!(result.is_err());
1264 assert_eq!(
1265 result.unwrap_err(),
1266 "clientDataJSON challenge does not match transaction hash"
1267 );
1268 }
1269
1270 #[test]
1271 fn test_webauthn_data_verification_valid() {
1272 let tx_hash = B256::from_slice(&[0xAA; 32]);
1273 let webauthn_data = build_webauthn_data(0x01, None, &tx_hash); let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1276 assert!(
1277 result.is_ok(),
1278 "Valid WebAuthn data should verify successfully"
1279 );
1280
1281 let message_hash = result.unwrap();
1283 let auth_data = &webauthn_data[..37];
1284 let client_data = &webauthn_data[37..];
1285
1286 let client_data_hash = Sha256::digest(client_data);
1287 let mut final_hasher = Sha256::new();
1288 final_hasher.update(auth_data);
1289 final_hasher.update(client_data_hash);
1290 let expected_hash = final_hasher.finalize();
1291
1292 assert_eq!(message_hash.as_slice(), &expected_hash[..]);
1293 }
1294
1295 #[test]
1296 fn test_p256_address_derivation() {
1297 let pub_key_x =
1298 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1299 let pub_key_y =
1300 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1301
1302 let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1303 let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1304
1305 assert_eq!(addr1, addr2);
1307
1308 assert_ne!(addr1, Address::ZERO);
1310 }
1311
1312 #[test]
1313 fn test_p256_address_derivation_deterministic() {
1314 let pub_key_x =
1316 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1317 let pub_key_y =
1318 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1319
1320 let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1321 let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1322
1323 assert_eq!(addr1, addr2, "Address derivation should be deterministic");
1324 }
1325
1326 #[test]
1327 fn test_p256_address_different_keys_different_addresses() {
1328 let pub_key_x1 =
1330 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1331 let pub_key_y1 =
1332 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1333
1334 let pub_key_x2 =
1335 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1336 let pub_key_y2 =
1337 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1338
1339 let addr1 = derive_p256_address(&pub_key_x1, &pub_key_y1);
1340 let addr2 = derive_p256_address(&pub_key_x2, &pub_key_y2);
1341
1342 assert_ne!(
1343 addr1, addr2,
1344 "Different keys should produce different addresses"
1345 );
1346 }
1347
1348 #[test]
1349 fn test_tempo_signature_from_bytes_secp256k1() {
1350 use super::SECP256K1_SIGNATURE_LENGTH;
1351
1352 let sig_bytes = vec![0u8; SECP256K1_SIGNATURE_LENGTH];
1354 let result = TempoSignature::from_bytes(&sig_bytes);
1355
1356 assert!(result.is_ok());
1357 if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(_)) = result.unwrap() {
1358 } else {
1360 panic!("Expected Primitive(Secp256k1) variant");
1361 }
1362 }
1363
1364 #[test]
1365 fn test_tempo_signature_from_bytes_p256() {
1366 use super::{P256_SIGNATURE_LENGTH, SIGNATURE_TYPE_P256};
1367
1368 let mut sig_bytes = vec![SIGNATURE_TYPE_P256];
1369 sig_bytes.extend_from_slice(&[0u8; P256_SIGNATURE_LENGTH]);
1370 let result = TempoSignature::from_bytes(&sig_bytes);
1371
1372 assert!(result.is_ok());
1373 if let TempoSignature::Primitive(PrimitiveSignature::P256(_)) = result.unwrap() {
1374 } else {
1376 panic!("Expected Primitive(P256) variant");
1377 }
1378 }
1379
1380 #[test]
1381 fn test_tempo_signature_from_bytes_webauthn() {
1382 use super::SIGNATURE_TYPE_WEBAUTHN;
1383
1384 let mut sig_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1385 sig_bytes.extend_from_slice(&[0u8; 200]); let result = TempoSignature::from_bytes(&sig_bytes);
1387
1388 assert!(result.is_ok());
1389 if let TempoSignature::Primitive(PrimitiveSignature::WebAuthn(_)) = result.unwrap() {
1390 } else {
1392 panic!("Expected Primitive(WebAuthn) variant");
1393 }
1394 }
1395
1396 #[test]
1397 fn test_tempo_signature_from_bytes_validation() {
1398 assert_eq!(
1400 TempoSignature::from_bytes(&[]).unwrap_err(),
1401 "Signature data is empty"
1402 );
1403 assert_eq!(
1404 PrimitiveSignature::from_bytes(&[]).unwrap_err(),
1405 "Signature data is empty"
1406 );
1407
1408 assert_eq!(
1410 TempoSignature::from_bytes(&[0x01]).unwrap_err(),
1411 "Signature data too short: expected type identifier + signature data"
1412 );
1413
1414 let mut bad_p256 = vec![SIGNATURE_TYPE_P256];
1416 bad_p256.extend_from_slice(&[0u8; 100]); assert_eq!(
1418 TempoSignature::from_bytes(&bad_p256).unwrap_err(),
1419 "Invalid P256 signature length"
1420 );
1421
1422 let mut bad_webauthn = vec![SIGNATURE_TYPE_WEBAUTHN];
1424 bad_webauthn.extend_from_slice(&[0u8; 50]); assert_eq!(
1426 TempoSignature::from_bytes(&bad_webauthn).unwrap_err(),
1427 "Invalid WebAuthn signature length"
1428 );
1429
1430 let mut unknown_type = vec![0xFF];
1432 unknown_type.extend_from_slice(&[0u8; 100]);
1433 assert_eq!(
1434 TempoSignature::from_bytes(&unknown_type).unwrap_err(),
1435 "Unknown signature type identifier"
1436 );
1437 }
1438
1439 #[test]
1440 fn test_tempo_signature_roundtrip() {
1441 use super::{
1442 P256_SIGNATURE_LENGTH, SECP256K1_SIGNATURE_LENGTH, SIGNATURE_TYPE_P256,
1443 SIGNATURE_TYPE_WEBAUTHN,
1444 };
1445
1446 let sig1_bytes = vec![1u8; SECP256K1_SIGNATURE_LENGTH];
1448 let sig1 = TempoSignature::from_bytes(&sig1_bytes).unwrap();
1449 let encoded1 = sig1.to_bytes();
1450 assert_eq!(encoded1.len(), SECP256K1_SIGNATURE_LENGTH); let decoded1 = TempoSignature::from_bytes(&encoded1).unwrap();
1453 assert_eq!(sig1, decoded1);
1454
1455 let mut sig2_bytes = vec![SIGNATURE_TYPE_P256];
1457 sig2_bytes.extend_from_slice(&[2u8; P256_SIGNATURE_LENGTH]);
1458 let sig2 = TempoSignature::from_bytes(&sig2_bytes).unwrap();
1459 let encoded2 = sig2.to_bytes();
1460 assert_eq!(encoded2.len(), 1 + P256_SIGNATURE_LENGTH);
1461 let decoded2 = TempoSignature::from_bytes(&encoded2).unwrap();
1463 assert_eq!(sig2, decoded2);
1464
1465 let mut sig3_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1467 sig3_bytes.extend_from_slice(&[3u8; 200]);
1468 let sig3 = TempoSignature::from_bytes(&sig3_bytes).unwrap();
1469 let encoded3 = sig3.to_bytes();
1470 assert_eq!(encoded3.len(), 1 + 200);
1471 let decoded3 = TempoSignature::from_bytes(&encoded3).unwrap();
1473 assert_eq!(sig3, decoded3);
1474 }
1475
1476 #[test]
1477 #[cfg(feature = "serde")]
1478 fn test_tempo_signature_serde_roundtrip() {
1479 let r_bytes = hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
1483 let s_bytes = hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321");
1484 let sig = Signature::new(
1485 alloy_primitives::U256::from_be_slice(&r_bytes),
1486 alloy_primitives::U256::from_be_slice(&s_bytes),
1487 false,
1488 );
1489 let secp256k1_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig));
1490
1491 let json = serde_json::to_string(&secp256k1_sig).unwrap();
1492 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1493 assert_eq!(secp256k1_sig, decoded, "Secp256k1 serde roundtrip failed");
1494
1495 let p256_sig =
1497 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1498 r: B256::from([1u8; 32]),
1499 s: B256::from([2u8; 32]),
1500 pub_key_x: B256::from([3u8; 32]),
1501 pub_key_y: B256::from([4u8; 32]),
1502 pre_hash: true,
1503 }));
1504
1505 let json = serde_json::to_string(&p256_sig).unwrap();
1506 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1507 assert_eq!(p256_sig, decoded, "P256 serde roundtrip failed");
1508
1509 assert!(
1511 json.contains("\"pubKeyX\""),
1512 "Should use camelCase for pubKeyX"
1513 );
1514 assert!(
1515 json.contains("\"pubKeyY\""),
1516 "Should use camelCase for pubKeyY"
1517 );
1518 assert!(
1519 json.contains("\"preHash\""),
1520 "Should use camelCase for preHash"
1521 );
1522
1523 let webauthn_sig =
1525 TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1526 r: B256::from([5u8; 32]),
1527 s: B256::from([6u8; 32]),
1528 pub_key_x: B256::from([7u8; 32]),
1529 pub_key_y: B256::from([8u8; 32]),
1530 webauthn_data: Bytes::from(vec![9u8; 50]),
1531 }));
1532
1533 let json = serde_json::to_string(&webauthn_sig).unwrap();
1534 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1535 assert_eq!(webauthn_sig, decoded, "WebAuthn serde roundtrip failed");
1536
1537 assert!(
1539 json.contains("\"pubKeyX\""),
1540 "Should use camelCase for pubKeyX"
1541 );
1542 assert!(
1543 json.contains("\"pubKeyY\""),
1544 "Should use camelCase for pubKeyY"
1545 );
1546 assert!(
1547 json.contains("\"webauthnData\""),
1548 "Should use camelCase for webauthnData"
1549 );
1550 }
1551
1552 #[test]
1553 fn test_webauthn_flag_validation() {
1554 let tx_hash = B256::ZERO;
1555
1556 let data = build_webauthn_data(0x41, None, &tx_hash); let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1559 assert!(err.contains("AT flag"), "Should reject AT flag");
1560
1561 let data = build_webauthn_data(0x81, Some(&[0xa0]), &tx_hash); let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1564 assert!(err.contains("ED flag"), "Should reject ED flag");
1565
1566 let data = build_webauthn_data(0x01, None, &tx_hash); assert!(
1569 verify_webauthn_data_internal(&data, &tx_hash).is_ok(),
1570 "Should accept valid webauthn data with only UP flag"
1571 );
1572 }
1573
1574 #[test]
1575 fn test_recover_signer_p256() {
1576 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1577 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1578
1579 let sig_hash = B256::from([0xAA; 32]);
1580 let (r, s) = sign_p256_normalized(&signing_key, &sig_hash);
1581
1582 let p256_sig =
1583 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1584 r,
1585 s,
1586 pub_key_x,
1587 pub_key_y,
1588 pre_hash: false,
1589 }));
1590
1591 let recovered = p256_sig.recover_signer(&sig_hash).unwrap();
1592 assert_eq!(
1593 recovered, expected_address,
1594 "P256 recovery should match derived address"
1595 );
1596 }
1597
1598 #[test]
1599 fn test_recover_signer_p256_with_prehash() {
1600 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1601 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1602
1603 let sig_hash = B256::from([0xBB; 32]);
1605 let prehashed = B256::from_slice(Sha256::digest(sig_hash).as_ref());
1606 let (r, s) = sign_p256_normalized(&signing_key, &prehashed);
1607
1608 let p256_sig =
1609 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1610 r,
1611 s,
1612 pub_key_x,
1613 pub_key_y,
1614 pre_hash: true,
1615 }));
1616
1617 let recovered = p256_sig.recover_signer(&sig_hash).unwrap();
1618 assert_eq!(
1619 recovered, expected_address,
1620 "P256 pre_hash recovery should match"
1621 );
1622 }
1623
1624 #[test]
1625 fn test_recover_signer_p256_high_s_rejected() {
1626 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1627 let sig_hash = B256::from([0xCD; 32]);
1628 let signature: p256::ecdsa::Signature =
1629 signing_key.sign_prehash(sig_hash.as_slice()).unwrap();
1630 let sig_bytes = signature.to_bytes();
1631 let r = B256::from_slice(&sig_bytes[..32]);
1632 let s_value = U256::from_be_slice(&sig_bytes[32..64]);
1633 let high_s = if s_value > P256N_HALF {
1634 s_value
1635 } else {
1636 P256_ORDER - s_value
1637 };
1638
1639 let p256_sig =
1640 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1641 r,
1642 s: B256::from(high_s.to_be_bytes::<32>()),
1643 pub_key_x,
1644 pub_key_y,
1645 pre_hash: false,
1646 }));
1647
1648 assert!(
1649 p256_sig.recover_signer(&sig_hash).is_err(),
1650 "high-s P256 signatures must be rejected"
1651 );
1652 }
1653
1654 #[test]
1655 fn test_recover_signer_webauthn() {
1656 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1657 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1658
1659 let tx_hash = B256::from([0xCC; 32]);
1660 let webauthn_data = build_webauthn_data(0x01, None, &tx_hash);
1661
1662 let message_hash = verify_webauthn_data_internal(&webauthn_data, &tx_hash).unwrap();
1663
1664 let (r, s) = sign_p256_normalized(&signing_key, &message_hash);
1665
1666 let webauthn_sig =
1667 TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1668 r,
1669 s,
1670 pub_key_x,
1671 pub_key_y,
1672 webauthn_data: Bytes::from(webauthn_data),
1673 }));
1674
1675 let recovered = webauthn_sig.recover_signer(&tx_hash).unwrap();
1676 assert_eq!(
1677 recovered, expected_address,
1678 "WebAuthn recovery should match derived address"
1679 );
1680 }
1681
1682 #[test]
1683 fn test_recover_signer_webauthn_invalid_payload_rejected() {
1684 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1685 let tx_hash = B256::from([0xEF; 32]);
1686 let (r, s) = sign_p256_normalized(&signing_key, &B256::ZERO);
1687
1688 let invalid_webauthn_sig =
1689 TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1690 r,
1691 s,
1692 pub_key_x,
1693 pub_key_y,
1694 webauthn_data: Bytes::from(build_webauthn_data(0x41, None, &tx_hash)),
1695 }));
1696
1697 assert!(
1698 invalid_webauthn_sig.recover_signer(&tx_hash).is_err(),
1699 "invalid WebAuthn payloads must be rejected"
1700 );
1701 }
1702
1703 #[test]
1704 fn test_recover_signer_keychain_v1() {
1705 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1706
1707 let (signing_key, access_key_address) = generate_secp256k1_keypair();
1708 let user_address = Address::repeat_byte(0xDD);
1709
1710 let sig_hash = B256::from([0x22; 32]);
1712 let inner_sig = sign_hash(&signing_key, &sig_hash);
1713
1714 let keychain_sig = TempoSignature::Keychain(KeychainSignature::new_v1(
1715 user_address,
1716 match inner_sig {
1717 TempoSignature::Primitive(p) => p,
1718 _ => panic!("Expected primitive signature"),
1719 },
1720 ));
1721
1722 let recovered = keychain_sig.recover_signer(&sig_hash).unwrap();
1724 assert_eq!(
1725 recovered, user_address,
1726 "Keychain V1 recovery should return user_address"
1727 );
1728
1729 let keychain = keychain_sig.as_keychain().unwrap();
1731 let key_id = keychain.key_id(&sig_hash).unwrap();
1732 assert_eq!(
1733 key_id, access_key_address,
1734 "key_id should return access key address"
1735 );
1736
1737 assert!(keychain_sig.is_legacy_keychain());
1739 }
1740
1741 #[test]
1742 fn test_recover_signer_keychain_v2() {
1743 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1744
1745 let (signing_key, access_key_address) = generate_secp256k1_keypair();
1746 let user_address = Address::repeat_byte(0xDD);
1747
1748 let sig_hash = B256::from([0x22; 32]);
1750 let mut buf = [0u8; 53]; buf[0] = SIGNATURE_TYPE_KEYCHAIN_V2;
1752 buf[1..33].copy_from_slice(sig_hash.as_slice());
1753 buf[33..].copy_from_slice(user_address.as_slice());
1754 let effective_hash = keccak256(buf);
1755 let inner_sig = sign_hash(&signing_key, &effective_hash);
1756
1757 let keychain_sig = TempoSignature::Keychain(KeychainSignature::new(
1758 user_address,
1759 match inner_sig {
1760 TempoSignature::Primitive(p) => p,
1761 _ => panic!("Expected primitive signature"),
1762 },
1763 ));
1764
1765 let recovered = keychain_sig.recover_signer(&sig_hash).unwrap();
1767 assert_eq!(
1768 recovered, user_address,
1769 "Keychain V2 recovery should return user_address"
1770 );
1771
1772 let keychain = keychain_sig.as_keychain().unwrap();
1774 let key_id = keychain.key_id(&sig_hash).unwrap();
1775 assert_eq!(
1776 key_id, access_key_address,
1777 "key_id should return access key address"
1778 );
1779
1780 assert!(!keychain_sig.is_legacy_keychain());
1782 }
1783
1784 #[test]
1785 fn test_keychain_v2_binds_user_address() {
1786 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1787
1788 let (signing_key, _access_key_address) = generate_secp256k1_keypair();
1789 let user_a = Address::repeat_byte(0xAA);
1790 let user_b = Address::repeat_byte(0xBB);
1791
1792 let sig_hash = B256::from([0x22; 32]);
1794 let effective_hash = KeychainSignature::signing_hash(sig_hash, user_a);
1795 let inner_sig = sign_hash(&signing_key, &effective_hash);
1796
1797 let inner_primitive = match inner_sig {
1798 TempoSignature::Primitive(p) => p,
1799 _ => panic!("Expected primitive signature"),
1800 };
1801
1802 let sig_a =
1804 TempoSignature::Keychain(KeychainSignature::new(user_a, inner_primitive.clone()));
1805 let recovered_a = sig_a.recover_signer(&sig_hash).unwrap();
1806 assert_eq!(recovered_a, user_a);
1807
1808 let sig_b = TempoSignature::Keychain(KeychainSignature::new(user_b, inner_primitive));
1811 let recovered_b = sig_b.recover_signer(&sig_hash).unwrap();
1812 assert_eq!(
1813 recovered_b, user_b,
1814 "recover_signer returns the claimed user_address"
1815 );
1816
1817 let key_id_a = sig_a.as_keychain().unwrap().key_id(&sig_hash).unwrap();
1819 let key_id_b = sig_b.as_keychain().unwrap().key_id(&sig_hash).unwrap();
1820 assert_ne!(
1821 key_id_a, key_id_b,
1822 "V2 should recover different key_ids for different user_addresses"
1823 );
1824 }
1825
1826 #[test]
1827 fn test_signing_hash_properties() {
1828 let hash_a = B256::from([0x11; 32]);
1829 let hash_b = B256::from([0x22; 32]);
1830 let addr_a = Address::repeat_byte(0xAA);
1831 let addr_b = Address::repeat_byte(0xBB);
1832
1833 assert_ne!(
1835 KeychainSignature::signing_hash(hash_a, addr_a),
1836 KeychainSignature::signing_hash(hash_a, addr_b),
1837 );
1838
1839 assert_ne!(
1841 KeychainSignature::signing_hash(hash_a, addr_a),
1842 KeychainSignature::signing_hash(hash_b, addr_a),
1843 );
1844
1845 assert_eq!(
1847 KeychainSignature::signing_hash(hash_a, addr_a),
1848 KeychainSignature::signing_hash(hash_a, addr_a),
1849 );
1850 }
1851
1852 #[test]
1853 fn test_webauthn_rejects_challenge_injection() {
1854 let (tx_hash, attack_hash) = (B256::from([0xAA; 32]), B256::from([0xFF; 32]));
1855 let (challenge, attack_challenge) = (
1856 URL_SAFE_NO_PAD.encode(tx_hash.as_slice()),
1857 URL_SAFE_NO_PAD.encode(attack_hash.as_slice()),
1858 );
1859
1860 let valid_payload = format!(r#"{{"type":"webauthn.get","challenge":"{challenge}"}}"#);
1862
1863 let mut auth_data = vec![0u8; 37];
1864 auth_data[32] = 0x01;
1865 let mut webauthn_data = auth_data;
1866 webauthn_data.extend_from_slice(valid_payload.as_bytes());
1867
1868 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1869 assert!(result.is_ok());
1870
1871 let attack_variants = [
1873 format!(
1874 r#"{{"type":"webauthn.get","challenge":"{attack_challenge}","extra":{{"challenge":"{challenge}"}}}}"#
1875 ),
1876 format!(
1877 r#"{{"type":"webauthn.get","data":[{{"challenge":"{challenge}"}}],"challenge":"{attack_challenge}"}}"#
1878 ),
1879 ];
1880
1881 for (i, attack_json) in attack_variants.iter().enumerate() {
1882 let mut auth_data = vec![0u8; 37];
1883 auth_data[32] = 0x01;
1884 let mut webauthn_data = auth_data;
1885 webauthn_data.extend_from_slice(attack_json.as_bytes());
1886
1887 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1888 assert!(
1889 result.is_err(),
1890 "Attack variant {i} should be rejected: {attack_json}"
1891 );
1892 }
1893 }
1894
1895 #[test]
1896 fn test_keychain_signature_eq_same() {
1897 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1898 let addr = Address::repeat_byte(0x01);
1899 let a = KeychainSignature::new(addr, sig.clone());
1900 let b = KeychainSignature::new(addr, sig);
1901 assert_eq!(a, b);
1902 }
1903
1904 #[test]
1905 fn test_keychain_signature_eq_different_address() {
1906 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1907 let a = KeychainSignature::new(Address::repeat_byte(0x01), sig.clone());
1908 let b = KeychainSignature::new(Address::repeat_byte(0x02), sig);
1909 assert_ne!(a, b);
1910 }
1911
1912 #[test]
1913 fn test_keychain_signature_eq_different_signature() {
1914 let addr = Address::repeat_byte(0x01);
1915 let sig_a = PrimitiveSignature::Secp256k1(Signature::test_signature());
1916 let sig_b = PrimitiveSignature::P256(P256SignatureWithPreHash {
1917 r: B256::from([1u8; 32]),
1918 s: B256::from([2u8; 32]),
1919 pub_key_x: B256::from([3u8; 32]),
1920 pub_key_y: B256::from([4u8; 32]),
1921 pre_hash: false,
1922 });
1923 let a = KeychainSignature::new(addr, sig_a);
1924 let b = KeychainSignature::new(addr, sig_b);
1925 assert_ne!(a, b);
1926 }
1927
1928 #[test]
1929 fn test_keychain_signature_hash_differs_for_different_sigs() {
1930 use std::{
1931 collections::hash_map::DefaultHasher,
1932 hash::{Hash, Hasher},
1933 };
1934
1935 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1936 let a = KeychainSignature::new(Address::repeat_byte(0x01), sig.clone());
1937 let b = KeychainSignature::new(Address::repeat_byte(0x02), sig.clone());
1938 let c = KeychainSignature::new(Address::repeat_byte(0x01), sig);
1939
1940 let hash = |k: &KeychainSignature| {
1941 let mut h = DefaultHasher::new();
1942 k.hash(&mut h);
1943 h.finish()
1944 };
1945
1946 assert_ne!(
1947 hash(&a),
1948 hash(&b),
1949 "different address should produce different hash"
1950 );
1951 assert_eq!(hash(&a), hash(&c), "same fields should produce same hash");
1952 }
1953
1954 #[test]
1955 fn test_primitive_signature_from_bytes_one_byte() {
1956 let result = PrimitiveSignature::from_bytes(&[0x01]);
1957 assert!(result.is_err());
1958 assert!(result.unwrap_err().contains("too short"));
1959 }
1960
1961 #[test]
1962 fn test_tempo_signature_keychain_too_short_for_address() {
1963 for type_byte in [SIGNATURE_TYPE_KEYCHAIN, SIGNATURE_TYPE_KEYCHAIN_V2] {
1964 let mut data = vec![type_byte];
1965 data.extend_from_slice(&[0u8; 19]);
1966 let result = TempoSignature::from_bytes(&data);
1967 assert!(result.is_err());
1968 assert!(result.unwrap_err().contains("too short"));
1969 }
1970 }
1971
1972 #[test]
1973 fn test_tempo_signature_keychain_exactly_20_bytes_inner_empty() {
1974 let mut data = vec![SIGNATURE_TYPE_KEYCHAIN];
1975 data.extend_from_slice(&[0u8; 20]);
1976 let result = TempoSignature::from_bytes(&data);
1977 assert!(result.is_err());
1978 }
1979
1980 #[test]
1981 fn test_is_keychain_returns_false_for_primitive() {
1982 let sig =
1983 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1984 assert!(!sig.is_keychain());
1985 }
1986
1987 #[test]
1988 fn test_is_keychain_returns_true_for_keychain() {
1989 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
1990 let sig = TempoSignature::Keychain(KeychainSignature::new(Address::ZERO, inner));
1991 assert!(sig.is_keychain());
1992 }
1993
1994 #[test]
1995 fn test_keychain_v1_v2_bytes_roundtrip_and_wire_format() {
1996 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
1997 let user = Address::repeat_byte(0xAA);
1998
1999 let v1 = TempoSignature::Keychain(KeychainSignature::new_v1(user, inner.clone()));
2001 let v1_bytes = v1.to_bytes();
2002 assert_eq!(v1_bytes[0], SIGNATURE_TYPE_KEYCHAIN);
2003 let v1_decoded = TempoSignature::from_bytes(&v1_bytes).unwrap();
2004 assert_eq!(v1, v1_decoded);
2005 assert!(v1_decoded.is_legacy_keychain());
2006
2007 let v2 = TempoSignature::Keychain(KeychainSignature::new(user, inner));
2009 let v2_bytes = v2.to_bytes();
2010 assert_eq!(v2_bytes[0], SIGNATURE_TYPE_KEYCHAIN_V2);
2011 let v2_decoded = TempoSignature::from_bytes(&v2_bytes).unwrap();
2012 assert_eq!(v2, v2_decoded);
2013 assert!(!v2_decoded.is_legacy_keychain());
2014
2015 assert_ne!(v1, v2);
2017 }
2018
2019 #[test]
2020 #[cfg(feature = "serde")]
2021 fn test_keychain_serde_roundtrip_and_backward_compat() {
2022 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2023 let user = Address::repeat_byte(0xBB);
2024
2025 let v2 = TempoSignature::Keychain(KeychainSignature::new(user, inner.clone()));
2027 let json = serde_json::to_string(&v2).unwrap();
2028 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
2029 assert_eq!(v2, decoded);
2030 assert!(!decoded.is_legacy_keychain());
2031
2032 let v1 = TempoSignature::Keychain(KeychainSignature::new_v1(user, inner));
2034 let json_v1 = serde_json::to_string(&v1).unwrap();
2035 let decoded_v1: TempoSignature = serde_json::from_str(&json_v1).unwrap();
2036 assert_eq!(v1, decoded_v1);
2037 assert!(decoded_v1.is_legacy_keychain());
2038
2039 let json_no_version = json_v1.replace(r#","version":"v1""#, "");
2041 assert!(
2042 !json_no_version.contains("version"),
2043 "version field should be stripped"
2044 );
2045 let decoded_no_version: TempoSignature = serde_json::from_str(&json_no_version).unwrap();
2046 assert!(decoded_no_version.is_legacy_keychain());
2047 }
2048
2049 #[test]
2050 fn test_keychain_rlp_roundtrip_preserves_version() {
2051 use alloy_rlp::Decodable;
2052
2053 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2054 let user = Address::repeat_byte(0xCC);
2055
2056 for (sig, expect_legacy) in [
2057 (
2058 TempoSignature::Keychain(KeychainSignature::new_v1(user, inner.clone())),
2059 true,
2060 ),
2061 (
2062 TempoSignature::Keychain(KeychainSignature::new(user, inner)),
2063 false,
2064 ),
2065 ] {
2066 let mut buf = Vec::new();
2067 alloy_rlp::Encodable::encode(&sig, &mut buf);
2068 let decoded = TempoSignature::decode(&mut buf.as_slice()).unwrap();
2069 assert_eq!(sig, decoded);
2070 assert_eq!(decoded.is_legacy_keychain(), expect_legacy);
2071 }
2072 }
2073}
2074
2075#[cfg(all(test, feature = "reth-codec"))]
2076mod compact_tests {
2077 use super::*;
2078 use alloy_primitives::{b256, bytes, hex};
2079 use reth_codecs::Compact;
2080
2081 #[test]
2086 fn compact_types_have_unused_bits() {
2087 assert_ne!(
2088 P256SignatureWithPreHash::bitflag_unused_bits(),
2089 0,
2090 "P256SignatureWithPreHash"
2091 );
2092 }
2093
2094 #[test]
2095 fn p256_signature_compact_roundtrip() {
2096 let sig = P256SignatureWithPreHash {
2097 r: b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
2098 s: b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
2099 pub_key_x: b256!("0x3333333333333333333333333333333333333333333333333333333333333333"),
2100 pub_key_y: b256!("0x4444444444444444444444444444444444444444444444444444444444444444"),
2101 pre_hash: true,
2102 };
2103
2104 let expected = hex!(
2105 "011111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444"
2106 );
2107
2108 let mut buf = vec![];
2109 let len = sig.to_compact(&mut buf);
2110 assert_eq!(
2111 buf, expected,
2112 "P256SignatureWithPreHash compact encoding changed"
2113 );
2114 assert_eq!(len, expected.len());
2115
2116 let (decoded, _) = P256SignatureWithPreHash::from_compact(&expected, expected.len());
2117 assert_eq!(decoded, sig);
2118 }
2119
2120 #[test]
2121 fn webauthn_signature_compact_roundtrip() {
2122 let sig = WebAuthnSignature {
2123 r: b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
2124 s: b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
2125 pub_key_x: b256!("0x3333333333333333333333333333333333333333333333333333333333333333"),
2126 pub_key_y: b256!("0x4444444444444444444444444444444444444444444444444444444444444444"),
2127 webauthn_data: bytes!("aabbccdd"),
2128 };
2129
2130 let expected = hex!(
2131 "1111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444aabbccdd"
2132 );
2133
2134 let mut buf = vec![];
2135 let len = sig.to_compact(&mut buf);
2136 assert_eq!(buf, expected, "WebAuthnSignature compact encoding changed");
2137 assert_eq!(len, expected.len());
2138
2139 let (decoded, _) = WebAuthnSignature::from_compact(&expected, expected.len());
2140 assert_eq!(decoded, sig);
2141 }
2142}