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 p256::{
8 EncodedPoint,
9 ecdsa::{Signature as P256Signature, VerifyingKey, signature::hazmat::PrehashVerifier},
10};
11use sha2::{Digest, Sha256};
12
13#[cfg(not(feature = "std"))]
14use once_cell::race::OnceBox as OnceLock;
15#[cfg(feature = "std")]
16use std::sync::OnceLock;
17
18pub const P256_ORDER: U256 =
20 uint!(0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551_U256);
21
22pub const P256N_HALF: U256 =
28 uint!(0x7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8_U256);
29
30pub fn normalize_p256_s(s_bytes: &[u8]) -> B256 {
39 let s = U256::from_be_slice(s_bytes);
40 let normalized_s = if s > P256N_HALF { P256_ORDER - s } else { s };
41 B256::from(normalized_s.to_be_bytes::<32>())
42}
43
44pub const SIGNATURE_TYPE_P256: u8 = 0x01;
47pub const SIGNATURE_TYPE_WEBAUTHN: u8 = 0x02;
48pub const SIGNATURE_TYPE_KEYCHAIN: u8 = 0x03;
49pub const SIGNATURE_TYPE_KEYCHAIN_V2: u8 = 0x04;
50
51const MIN_AUTH_DATA_LEN: usize = 37;
53
54const UP: u8 = 0x01; const UV: u8 = 0x04; const AT: u8 = 0x40; const ED: u8 = 0x80; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
65#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
66#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
67#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact))]
68pub struct P256SignatureWithPreHash {
69 pub r: B256,
70 pub s: B256,
71 pub pub_key_x: B256,
72 pub pub_key_y: B256,
73 pub pre_hash: bool,
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Hash)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
80#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
81#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
82#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact))]
83pub struct WebAuthnSignature {
84 pub r: B256,
85 pub s: B256,
86 pub pub_key_x: B256,
87 pub pub_key_y: B256,
88 pub webauthn_data: Bytes,
90}
91
92#[derive(Clone, Debug, PartialEq, Eq, Hash)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))]
101#[cfg_attr(
102 all(test, feature = "reth-codec"),
103 reth_codecs::add_arbitrary_tests(compact, rlp)
104)]
105#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
106pub enum PrimitiveSignature {
107 Secp256k1(Signature),
109
110 P256(P256SignatureWithPreHash),
112
113 WebAuthn(WebAuthnSignature),
115}
116
117impl PrimitiveSignature {
118 pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
124 if data.is_empty() {
125 return Err("Signature data is empty");
126 }
127
128 if data.len() == SECP256K1_SIGNATURE_LENGTH {
130 let sig = Signature::try_from(data)
131 .map_err(|_| "Failed to parse secp256k1 signature: invalid signature values")?;
132 return Ok(Self::Secp256k1(sig));
133 }
134
135 if data.len() < 2 {
137 return Err("Signature data too short: expected type identifier + signature data");
138 }
139
140 let type_id = data[0];
141 let sig_data = &data[1..];
142
143 match type_id {
144 SIGNATURE_TYPE_P256 => {
145 if sig_data.len() != P256_SIGNATURE_LENGTH {
146 return Err("Invalid P256 signature length");
147 }
148 Ok(Self::P256(P256SignatureWithPreHash {
149 r: B256::from_slice(&sig_data[0..32]),
150 s: B256::from_slice(&sig_data[32..64]),
151 pub_key_x: B256::from_slice(&sig_data[64..96]),
152 pub_key_y: B256::from_slice(&sig_data[96..128]),
153 pre_hash: sig_data[128] != 0,
154 }))
155 }
156 SIGNATURE_TYPE_WEBAUTHN => {
157 let len = sig_data.len();
158 if !(128..=MAX_WEBAUTHN_SIGNATURE_LENGTH).contains(&len) {
159 return Err("Invalid WebAuthn signature length");
160 }
161 Ok(Self::WebAuthn(WebAuthnSignature {
162 r: B256::from_slice(&sig_data[len - 128..len - 96]),
163 s: B256::from_slice(&sig_data[len - 96..len - 64]),
164 pub_key_x: B256::from_slice(&sig_data[len - 64..len - 32]),
165 pub_key_y: B256::from_slice(&sig_data[len - 32..]),
166 webauthn_data: Bytes::copy_from_slice(&sig_data[..len - 128]),
167 }))
168 }
169
170 _ => Err("Unknown signature type identifier"),
171 }
172 }
173
174 pub fn to_bytes(&self) -> Bytes {
180 match self {
181 Self::Secp256k1(sig) => {
182 let sig_bytes = sig.as_bytes();
185 assert_eq!(
186 sig_bytes.len(),
187 SECP256K1_SIGNATURE_LENGTH,
188 "Secp256k1 signature must be exactly 65 bytes"
189 );
190 Bytes::copy_from_slice(&sig_bytes)
191 }
192 Self::P256(p256_sig) => {
193 let mut bytes = Vec::with_capacity(1 + 129);
194 bytes.push(SIGNATURE_TYPE_P256);
195 bytes.extend_from_slice(p256_sig.r.as_slice());
196 bytes.extend_from_slice(p256_sig.s.as_slice());
197 bytes.extend_from_slice(p256_sig.pub_key_x.as_slice());
198 bytes.extend_from_slice(p256_sig.pub_key_y.as_slice());
199 bytes.push(if p256_sig.pre_hash { 1 } else { 0 });
200 Bytes::from(bytes)
201 }
202 Self::WebAuthn(webauthn_sig) => {
203 let mut bytes = Vec::with_capacity(1 + webauthn_sig.webauthn_data.len() + 128);
204 bytes.push(SIGNATURE_TYPE_WEBAUTHN);
205 bytes.extend_from_slice(&webauthn_sig.webauthn_data);
206 bytes.extend_from_slice(webauthn_sig.r.as_slice());
207 bytes.extend_from_slice(webauthn_sig.s.as_slice());
208 bytes.extend_from_slice(webauthn_sig.pub_key_x.as_slice());
209 bytes.extend_from_slice(webauthn_sig.pub_key_y.as_slice());
210 Bytes::from(bytes)
211 }
212 }
213 }
214
215 pub fn encoded_length(&self) -> usize {
221 match self {
222 Self::Secp256k1(_) => SECP256K1_SIGNATURE_LENGTH,
223 Self::P256(_) => 1 + P256_SIGNATURE_LENGTH,
224 Self::WebAuthn(webauthn_sig) => 1 + webauthn_sig.webauthn_data.len() + 128,
225 }
226 }
227
228 pub fn signature_type(&self) -> SignatureType {
230 match self {
231 Self::Secp256k1(_) => SignatureType::Secp256k1,
232 Self::P256(_) => SignatureType::P256,
233 Self::WebAuthn(_) => SignatureType::WebAuthn,
234 }
235 }
236
237 pub fn size(&self) -> usize {
239 size_of::<Self>()
240 + match self {
241 Self::Secp256k1(_) | Self::P256(_) => 0,
242 Self::WebAuthn(webauthn_sig) => webauthn_sig.webauthn_data.len(),
243 }
244 }
245
246 pub fn recover_signer(
253 &self,
254 sig_hash: &B256,
255 ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
256 match self {
257 Self::Secp256k1(sig) => {
258 alloy_consensus::crypto::secp256k1::recover_signer(sig, *sig_hash)
261 }
262 Self::P256(p256_sig) => {
263 let message_hash = if p256_sig.pre_hash {
265 B256::from_slice(Sha256::digest(sig_hash).as_ref())
267 } else {
268 *sig_hash
269 };
270
271 verify_p256_signature_internal(
273 p256_sig.r.as_slice(),
274 p256_sig.s.as_slice(),
275 p256_sig.pub_key_x.as_slice(),
276 p256_sig.pub_key_y.as_slice(),
277 &message_hash,
278 )
279 .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
280
281 Ok(derive_p256_address(
283 &p256_sig.pub_key_x,
284 &p256_sig.pub_key_y,
285 ))
286 }
287 Self::WebAuthn(webauthn_sig) => {
288 let message_hash =
290 verify_webauthn_data_internal(&webauthn_sig.webauthn_data, sig_hash)
291 .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
292
293 verify_p256_signature_internal(
295 webauthn_sig.r.as_slice(),
296 webauthn_sig.s.as_slice(),
297 webauthn_sig.pub_key_x.as_slice(),
298 webauthn_sig.pub_key_y.as_slice(),
299 &message_hash,
300 )
301 .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
302
303 Ok(derive_p256_address(
305 &webauthn_sig.pub_key_x,
306 &webauthn_sig.pub_key_y,
307 ))
308 }
309 }
310 }
311}
312
313impl Default for PrimitiveSignature {
314 fn default() -> Self {
315 Self::Secp256k1(Signature::test_signature())
316 }
317}
318
319impl alloy_rlp::Encodable for PrimitiveSignature {
320 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
321 let bytes = self.to_bytes();
322 alloy_rlp::Encodable::encode(&bytes, out);
323 }
324
325 fn length(&self) -> usize {
326 self.to_bytes().length()
327 }
328}
329
330impl alloy_rlp::Decodable for PrimitiveSignature {
331 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
332 let bytes: Bytes = alloy_rlp::Decodable::decode(buf)?;
333 Self::from_bytes(&bytes).map_err(alloy_rlp::Error::Custom)
334 }
335}
336
337#[cfg(feature = "reth-codec")]
338impl reth_codecs::Compact for PrimitiveSignature {
339 fn to_compact<B>(&self, buf: &mut B) -> usize
340 where
341 B: alloy_rlp::BufMut + AsMut<[u8]>,
342 {
343 let bytes = self.to_bytes();
344 bytes.to_compact(buf)
346 }
347
348 fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
349 let (bytes, rest) = Bytes::from_compact(buf, len);
351 let signature = Self::from_bytes(&bytes)
352 .expect("Failed to decode PrimitiveSignature from compact encoding");
353 (signature, rest)
354 }
355}
356
357#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
361#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
362#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
363#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
364pub enum KeychainVersion {
365 #[default]
369 V1,
370 V2,
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380pub enum KeychainVersionError {
381 LegacyPostT1C,
383 V2BeforeActivation,
385}
386
387#[derive(Clone, Debug)]
401#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
402#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
403pub struct KeychainSignature {
404 pub user_address: Address,
406 pub signature: PrimitiveSignature,
408 #[cfg_attr(feature = "serde", serde(default))]
410 pub version: KeychainVersion,
411 #[cfg_attr(
416 feature = "serde",
417 serde(
418 serialize_with = "serialize_once_lock",
419 rename = "keyId",
420 skip_deserializing,
421 )
422 )]
423 cached_key_id: OnceLock<Address>,
424}
425
426impl KeychainSignature {
427 pub fn new(user_address: Address, signature: PrimitiveSignature) -> Self {
431 Self {
432 user_address,
433 signature,
434 version: KeychainVersion::V2,
435 cached_key_id: OnceLock::new(),
436 }
437 }
438
439 pub fn new_v1(user_address: Address, signature: PrimitiveSignature) -> Self {
444 Self {
445 user_address,
446 signature,
447 version: KeychainVersion::V1,
448 cached_key_id: OnceLock::new(),
449 }
450 }
451
452 fn effective_sig_hash(&self, sig_hash: &B256) -> B256 {
457 match self.version {
458 KeychainVersion::V1 => *sig_hash,
459 KeychainVersion::V2 => Self::signing_hash(*sig_hash, self.user_address),
460 }
461 }
462
463 pub fn key_id(
471 &self,
472 sig_hash: &B256,
473 ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
474 if let Some(cached) = self.cached_key_id.get() {
476 return Ok(*cached);
477 }
478
479 let effective_hash = self.effective_sig_hash(sig_hash);
481 let key_id = self.signature.recover_signer(&effective_hash)?;
482 #[allow(clippy::useless_conversion)]
483 let _ = self.cached_key_id.set(key_id.into());
484 Ok(key_id)
485 }
486
487 pub fn is_legacy(&self) -> bool {
489 self.version == KeychainVersion::V1
490 }
491
492 pub fn signing_hash(sig_hash: B256, user_address: Address) -> B256 {
499 let mut buf = [0u8; 53]; buf[0] = SIGNATURE_TYPE_KEYCHAIN_V2;
501 buf[1..33].copy_from_slice(sig_hash.as_slice());
502 buf[33..].copy_from_slice(user_address.as_slice());
503 keccak256(buf)
504 }
505}
506
507impl PartialEq for KeychainSignature {
510 fn eq(&self, other: &Self) -> bool {
511 self.user_address == other.user_address
512 && self.signature == other.signature
513 && self.version == other.version
514 }
515}
516
517impl Eq for KeychainSignature {}
518
519impl core::hash::Hash for KeychainSignature {
520 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
521 self.user_address.hash(state);
522 self.signature.hash(state);
523 self.version.hash(state);
524 }
525}
526
527#[cfg(any(test, feature = "arbitrary"))]
529impl<'a> arbitrary::Arbitrary<'a> for KeychainSignature {
530 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
531 Ok(Self {
532 user_address: u.arbitrary()?,
533 signature: u.arbitrary()?,
534 version: u.arbitrary()?,
535 cached_key_id: OnceLock::new(), })
537 }
538}
539
540#[derive(Clone, Debug, PartialEq, Eq, Hash)]
544#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
545#[cfg_attr(feature = "serde", serde(untagged, rename_all = "camelCase"))]
546#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
547#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
548pub enum TempoSignature {
549 Primitive(PrimitiveSignature),
551
552 Keychain(KeychainSignature),
557}
558
559impl TempoSignature {
560 pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
566 if data.is_empty() {
567 return Err("Signature data is empty");
568 }
569
570 if data.len() > 1
573 && data.len() != SECP256K1_SIGNATURE_LENGTH
574 && (data[0] == SIGNATURE_TYPE_KEYCHAIN || data[0] == SIGNATURE_TYPE_KEYCHAIN_V2)
575 {
576 let version = if data[0] == SIGNATURE_TYPE_KEYCHAIN {
577 KeychainVersion::V1
578 } else {
579 KeychainVersion::V2
580 };
581 let sig_data = &data[1..];
582
583 if sig_data.len() < 20 {
585 return Err("Invalid Keychain signature: too short for user_address");
586 }
587
588 let user_address = Address::from_slice(&sig_data[0..20]);
589 let inner_sig_bytes = &sig_data[20..];
590
591 let inner_signature = PrimitiveSignature::from_bytes(inner_sig_bytes)?;
594
595 return Ok(Self::Keychain(KeychainSignature {
596 user_address,
597 signature: inner_signature,
598 version,
599 cached_key_id: OnceLock::new(),
600 }));
601 }
602
603 let primitive = PrimitiveSignature::from_bytes(data)?;
605 Ok(Self::Primitive(primitive))
606 }
607
608 pub fn to_bytes(&self) -> Bytes {
614 match self {
615 Self::Primitive(primitive_sig) => primitive_sig.to_bytes(),
616 Self::Keychain(keychain_sig) => {
617 let inner_bytes = keychain_sig.signature.to_bytes();
619 let mut bytes = Vec::with_capacity(1 + 20 + inner_bytes.len());
620 let type_byte = match keychain_sig.version {
621 KeychainVersion::V1 => SIGNATURE_TYPE_KEYCHAIN,
622 KeychainVersion::V2 => SIGNATURE_TYPE_KEYCHAIN_V2,
623 };
624 bytes.push(type_byte);
625 bytes.extend_from_slice(keychain_sig.user_address.as_slice());
626 bytes.extend_from_slice(&inner_bytes);
627 Bytes::from(bytes)
628 }
629 }
630 }
631
632 pub fn encoded_length(&self) -> usize {
638 match self {
639 Self::Primitive(primitive_sig) => primitive_sig.encoded_length(),
640 Self::Keychain(keychain_sig) => 1 + 20 + keychain_sig.signature.encoded_length(),
641 }
642 }
643
644 pub fn signature_type(&self) -> SignatureType {
646 match self {
647 Self::Primitive(primitive_sig) => primitive_sig.signature_type(),
648 Self::Keychain(keychain_sig) => keychain_sig.signature.signature_type(),
649 }
650 }
651
652 pub fn size(&self) -> usize {
654 match self {
655 Self::Primitive(primitive_sig) => primitive_sig.size(),
656 Self::Keychain(keychain_sig) => 1 + 20 + keychain_sig.signature.size(),
657 }
658 }
659
660 pub fn recover_signer(
675 &self,
676 sig_hash: &B256,
677 ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
678 match self {
679 Self::Primitive(primitive_sig) => primitive_sig.recover_signer(sig_hash),
680 Self::Keychain(keychain_sig) => {
681 keychain_sig.key_id(sig_hash)?;
683
684 Ok(keychain_sig.user_address)
686 }
687 }
688 }
689
690 pub fn is_keychain(&self) -> bool {
692 matches!(self, Self::Keychain(_))
693 }
694
695 pub fn is_legacy_keychain(&self) -> bool {
697 matches!(self, Self::Keychain(k) if k.is_legacy())
698 }
699
700 pub fn is_v2_keychain(&self) -> bool {
702 matches!(
703 self,
704 Self::Keychain(KeychainSignature {
705 version: KeychainVersion::V2,
706 ..
707 })
708 )
709 }
710
711 pub fn validate_version(&self, is_t1c: bool) -> Result<(), KeychainVersionError> {
716 if is_t1c && self.is_legacy_keychain() {
717 return Err(KeychainVersionError::LegacyPostT1C);
718 }
719 if !is_t1c && self.is_v2_keychain() {
720 return Err(KeychainVersionError::V2BeforeActivation);
721 }
722 Ok(())
723 }
724
725 pub fn as_keychain(&self) -> Option<&KeychainSignature> {
727 match self {
728 Self::Keychain(keychain_sig) => Some(keychain_sig),
729 _ => None,
730 }
731 }
732}
733
734impl Default for TempoSignature {
735 fn default() -> Self {
736 Self::Primitive(PrimitiveSignature::default())
737 }
738}
739
740impl alloy_rlp::Encodable for TempoSignature {
741 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
742 let bytes = self.to_bytes();
743 alloy_rlp::Encodable::encode(&bytes, out);
744 }
745
746 fn length(&self) -> usize {
747 self.to_bytes().length()
748 }
749}
750
751impl alloy_rlp::Decodable for TempoSignature {
752 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
753 let bytes: Bytes = alloy_rlp::Decodable::decode(buf)?;
754 Self::from_bytes(&bytes).map_err(alloy_rlp::Error::Custom)
755 }
756}
757
758#[cfg(feature = "reth-codec")]
759impl reth_codecs::Compact for TempoSignature {
760 fn to_compact<B>(&self, buf: &mut B) -> usize
761 where
762 B: alloy_rlp::BufMut + AsMut<[u8]>,
763 {
764 let bytes = self.to_bytes();
765 bytes.to_compact(buf)
767 }
768
769 fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
770 let (bytes, rest) = Bytes::from_compact(buf, len);
772 let signature = Self::from_bytes(&bytes)
773 .expect("Failed to decode TempoSignature from compact encoding");
774 (signature, rest)
775 }
776}
777
778impl From<Signature> for TempoSignature {
779 fn from(signature: Signature) -> Self {
780 Self::Primitive(PrimitiveSignature::Secp256k1(signature))
781 }
782}
783
784pub fn derive_p256_address(pub_key_x: &B256, pub_key_y: &B256) -> Address {
790 let hash = keccak256([pub_key_x.as_slice(), pub_key_y.as_slice()].concat());
791
792 Address::from_slice(&hash[12..])
794}
795
796fn verify_p256_signature_internal(
807 r: &[u8],
808 s: &[u8],
809 pub_key_x: &[u8],
810 pub_key_y: &[u8],
811 message_hash: &B256,
812) -> Result<(), &'static str> {
813 let s_value = U256::from_be_slice(s);
815 if s_value > P256N_HALF {
816 return Err("P256 signature has high s value");
817 }
818
819 let encoded_point = EncodedPoint::from_affine_coordinates(
821 pub_key_x.into(),
822 pub_key_y.into(),
823 false, );
825
826 let verifying_key =
827 VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| "Invalid P256 public key")?;
828
829 let signature = P256Signature::from_slice(&[r, s].concat())
830 .map_err(|_| "Invalid P256 signature encoding")?;
831
832 verifying_key
834 .verify_prehash(message_hash.as_slice(), &signature)
835 .map_err(|_| "P256 signature verification failed")
836}
837
838#[derive(serde::Deserialize)]
841struct ClientDataJson<'a> {
842 #[serde(rename = "type")]
843 type_field: &'a str,
844 challenge: &'a str,
845}
846
847fn verify_webauthn_data_internal(
855 webauthn_data: &[u8],
856 tx_hash: &B256,
857) -> Result<B256, &'static str> {
858 if webauthn_data.len() < MIN_AUTH_DATA_LEN + 32 {
860 return Err("WebAuthn data too short");
861 }
862
863 let flags = webauthn_data[32];
865 let (up_flag, uv_flag, at_flag, ed_flag) = (flags & UP, flags & UV, flags & AT, flags & ED);
866
867 if up_flag == 0 && uv_flag == 0 {
869 return Err("neither UP, nor UV flag set");
870 }
871
872 if at_flag != 0 {
874 return Err("AT flag must not be set for assertion signatures");
875 }
876
877 let auth_data_len = if ed_flag == 0 {
879 MIN_AUTH_DATA_LEN
881 } else {
882 return Err("ED flag must not be set, as Tempo doesn't support extensions");
885 };
886
887 let authenticator_data = &webauthn_data[..auth_data_len];
888 let client_data_json = &webauthn_data[auth_data_len..];
889
890 let client_data: ClientDataJson<'_> =
893 serde_json::from_slice(client_data_json).map_err(|_| "clientDataJSON is not valid JSON")?;
894
895 if client_data.type_field != "webauthn.get" {
897 return Err("clientDataJSON type must be webauthn.get");
898 }
899
900 if client_data.challenge != URL_SAFE_NO_PAD.encode(tx_hash.as_slice()) {
902 return Err("clientDataJSON challenge does not match transaction hash");
903 }
904
905 let client_data_hash = Sha256::digest(client_data_json);
908
909 let mut final_hasher = Sha256::new();
910 final_hasher.update(authenticator_data);
911 final_hasher.update(client_data_hash);
912 let message_hash = final_hasher.finalize();
913
914 Ok(B256::from_slice(&message_hash))
915}
916
917#[cfg(feature = "serde")]
918fn serialize_once_lock<S>(value: &OnceLock<Address>, serializer: S) -> Result<S::Ok, S::Error>
920where
921 S: serde::Serializer,
922{
923 serde::Serialize::serialize(&value.get(), serializer)
924}
925
926#[cfg(test)]
927mod tests {
928 use super::*;
929 use alloy_primitives::hex;
930 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
931 use p256::{
932 ecdsa::{SigningKey as P256SigningKey, signature::hazmat::PrehashSigner},
933 elliptic_curve::rand_core::OsRng,
934 };
935
936 fn generate_p256_keypair() -> (P256SigningKey, B256, B256) {
938 let signing_key = P256SigningKey::random(&mut OsRng);
939 let verifying_key = signing_key.verifying_key();
940 let encoded_point = verifying_key.to_encoded_point(false);
941 let pub_key_x = B256::from_slice(encoded_point.x().unwrap().as_ref());
942 let pub_key_y = B256::from_slice(encoded_point.y().unwrap().as_ref());
943 (signing_key, pub_key_x, pub_key_y)
944 }
945
946 fn sign_p256_normalized(signing_key: &P256SigningKey, message_hash: &B256) -> (B256, B256) {
948 let signature: p256::ecdsa::Signature =
949 signing_key.sign_prehash(message_hash.as_slice()).unwrap();
950 let sig_bytes = signature.to_bytes();
951 let r = B256::from_slice(&sig_bytes[0..32]);
952 let s = normalize_p256_s(&sig_bytes[32..64]);
953 (r, s)
954 }
955
956 fn build_webauthn_data(flags: u8, extension: Option<&[u8]>, tx_hash: &B256) -> Vec<u8> {
958 let mut data = vec![0u8; 32]; data.push(flags);
960 data.extend_from_slice(&[0u8; 4]); if let Some(ext) = extension {
962 data.extend_from_slice(ext);
963 }
964 let challenge = URL_SAFE_NO_PAD.encode(tx_hash.as_slice());
965 data.extend_from_slice(
966 format!("{{\"type\":\"webauthn.get\",\"challenge\":\"{challenge}\"}}").as_bytes(),
967 );
968 data
969 }
970
971 #[test]
972 fn test_p256_high_s_normalization() {
973 let low_s = U256::from(1u64);
975 let low_s_bytes: [u8; 32] = low_s.to_be_bytes();
976 assert_eq!(
977 U256::from_be_slice(normalize_p256_s(&low_s_bytes).as_slice()),
978 low_s,
979 "s < P256N_HALF should remain unchanged"
980 );
981
982 let half_bytes: [u8; 32] = P256N_HALF.to_be_bytes();
984 assert_eq!(
985 U256::from_be_slice(normalize_p256_s(&half_bytes).as_slice()),
986 P256N_HALF,
987 "s == P256N_HALF should remain unchanged"
988 );
989
990 let high_s = P256N_HALF + U256::from(1u64);
992 let high_s_bytes: [u8; 32] = high_s.to_be_bytes();
993 assert_eq!(
994 U256::from_be_slice(normalize_p256_s(&high_s_bytes).as_slice()),
995 P256_ORDER - high_s,
996 "s > P256N_HALF should be normalized"
997 );
998
999 let max_s = P256_ORDER - U256::from(1u64);
1001 let max_s_bytes: [u8; 32] = max_s.to_be_bytes();
1002 assert_eq!(
1003 U256::from_be_slice(normalize_p256_s(&max_s_bytes).as_slice()),
1004 U256::from(1u64),
1005 "s == P256_ORDER - 1 should normalize to 1"
1006 );
1007 }
1008
1009 #[test]
1010 fn test_p256_signature_verification_invalid_pubkey() {
1011 let r = [0u8; 32];
1013 let s = [0u8; 32];
1014 let pub_key_x = [0u8; 32]; let pub_key_y = [0u8; 32];
1016 let message_hash = B256::ZERO;
1017
1018 let result = verify_p256_signature_internal(&r, &s, &pub_key_x, &pub_key_y, &message_hash);
1019 assert!(result.is_err());
1020 }
1021
1022 #[test]
1023 fn test_p256_signature_verification_invalid_signature() {
1024 let (_, pub_key_x, pub_key_y) = generate_p256_keypair();
1025
1026 let r = [0u8; 32];
1028 let s = [0u8; 32];
1029 let message_hash = B256::ZERO;
1030
1031 let result = verify_p256_signature_internal(
1033 &r,
1034 &s,
1035 pub_key_x.as_slice(),
1036 pub_key_y.as_slice(),
1037 &message_hash,
1038 );
1039 assert!(
1040 result.is_err(),
1041 "Invalid signature should fail verification"
1042 );
1043 }
1044
1045 #[test]
1046 fn test_p256_signature_verification_valid() {
1047 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1048 let message_hash = B256::from_slice(&Sha256::digest(b"test message"));
1049 let (r, s) = sign_p256_normalized(&signing_key, &message_hash);
1050
1051 let result = verify_p256_signature_internal(
1052 r.as_slice(),
1053 s.as_slice(),
1054 pub_key_x.as_slice(),
1055 pub_key_y.as_slice(),
1056 &message_hash,
1057 );
1058 assert!(
1059 result.is_ok(),
1060 "Valid P256 signature should verify successfully"
1061 );
1062 }
1063
1064 #[test]
1065 fn test_p256_high_s_rejection() {
1066 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1067 let message_hash = B256::from_slice(&Sha256::digest(b"test message for high s"));
1068
1069 let signature: p256::ecdsa::Signature =
1071 signing_key.sign_prehash(message_hash.as_slice()).unwrap();
1072 let sig_bytes = signature.to_bytes();
1073 let r = &sig_bytes[0..32];
1074 let original_s = &sig_bytes[32..64];
1075
1076 let s_value = alloy_primitives::U256::from_be_slice(original_s);
1078 let computed_high_s = P256_ORDER - s_value;
1079 let computed_high_s_bytes: [u8; 32] = computed_high_s.to_be_bytes();
1080
1081 let s_is_low = s_value <= P256N_HALF;
1084 if s_is_low {
1085 let result = verify_p256_signature_internal(
1087 r,
1088 &computed_high_s_bytes,
1089 pub_key_x.as_slice(),
1090 pub_key_y.as_slice(),
1091 &message_hash,
1092 );
1093 assert!(
1094 result.is_err(),
1095 "High-s signature should be rejected for signature malleability prevention"
1096 );
1097 assert_eq!(result.unwrap_err(), "P256 signature has high s value");
1098 } else {
1099 let original_result = verify_p256_signature_internal(
1102 r,
1103 original_s,
1104 pub_key_x.as_slice(),
1105 pub_key_y.as_slice(),
1106 &message_hash,
1107 );
1108 assert!(
1109 original_result.is_err(),
1110 "Original high-s signature should be rejected"
1111 );
1112 }
1113 }
1114
1115 #[test]
1116 fn test_webauthn_data_verification_too_short() {
1117 let short_data = vec![0u8; 36];
1119 let tx_hash = B256::ZERO;
1120
1121 let result = verify_webauthn_data_internal(&short_data, &tx_hash);
1122 assert!(result.is_err());
1123 assert_eq!(result.unwrap_err(), "WebAuthn data too short");
1124 }
1125
1126 #[test]
1127 fn test_webauthn_data_verification_missing_up_and_uv_flags() {
1128 let tx_hash = B256::ZERO;
1129 let client_data = b"{\"type\":\"webauthn.get\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
1130
1131 let mut auth_data = vec![0u8; 37];
1133 auth_data[32] = 0x00;
1134 let mut webauthn_data = auth_data;
1135 webauthn_data.extend_from_slice(client_data);
1136
1137 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1138 assert!(result.is_err());
1139 assert_eq!(result.unwrap_err(), "neither UP, nor UV flag set");
1140
1141 let mut auth_data = vec![0u8; 37];
1143 auth_data[32] = 0x04;
1144 let mut webauthn_data = auth_data;
1145 webauthn_data.extend_from_slice(client_data);
1146
1147 assert!(verify_webauthn_data_internal(&webauthn_data, &tx_hash).is_ok());
1148 }
1149
1150 #[test]
1151 fn test_webauthn_data_verification_invalid_type() {
1152 let mut auth_data = vec![0u8; 37];
1154 auth_data[32] = 0x01; let client_data = b"{\"type\":\"webauthn.create\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
1158 let mut webauthn_data = auth_data;
1159 webauthn_data.extend_from_slice(client_data);
1160
1161 let tx_hash = B256::ZERO;
1162 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1163
1164 assert!(result.is_err());
1165 assert_eq!(
1166 result.unwrap_err(),
1167 "clientDataJSON type must be webauthn.get"
1168 );
1169 }
1170
1171 #[test]
1172 fn test_webauthn_data_verification_invalid_challenge() {
1173 let mut auth_data = vec![0u8; 37];
1175 auth_data[32] = 0x01; let client_data =
1179 b"{\"type\":\"webauthn.get\",\"challenge\":\"wrong_challenge_value_here\"}";
1180 let mut webauthn_data = auth_data;
1181 webauthn_data.extend_from_slice(client_data);
1182
1183 let tx_hash = B256::ZERO;
1184 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1185
1186 assert!(result.is_err());
1187 assert_eq!(
1188 result.unwrap_err(),
1189 "clientDataJSON challenge does not match transaction hash"
1190 );
1191 }
1192
1193 #[test]
1194 fn test_webauthn_data_verification_valid() {
1195 let tx_hash = B256::from_slice(&[0xAA; 32]);
1196 let webauthn_data = build_webauthn_data(0x01, None, &tx_hash); let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1199 assert!(
1200 result.is_ok(),
1201 "Valid WebAuthn data should verify successfully"
1202 );
1203
1204 let message_hash = result.unwrap();
1206 let auth_data = &webauthn_data[..37];
1207 let client_data = &webauthn_data[37..];
1208
1209 let client_data_hash = Sha256::digest(client_data);
1210 let mut final_hasher = Sha256::new();
1211 final_hasher.update(auth_data);
1212 final_hasher.update(client_data_hash);
1213 let expected_hash = final_hasher.finalize();
1214
1215 assert_eq!(message_hash.as_slice(), &expected_hash[..]);
1216 }
1217
1218 #[test]
1219 fn test_p256_address_derivation() {
1220 let pub_key_x =
1221 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1222 let pub_key_y =
1223 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1224
1225 let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1226 let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1227
1228 assert_eq!(addr1, addr2);
1230
1231 assert_ne!(addr1, Address::ZERO);
1233 }
1234
1235 #[test]
1236 fn test_p256_address_derivation_deterministic() {
1237 let pub_key_x =
1239 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1240 let pub_key_y =
1241 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1242
1243 let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1244 let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1245
1246 assert_eq!(addr1, addr2, "Address derivation should be deterministic");
1247 }
1248
1249 #[test]
1250 fn test_p256_address_different_keys_different_addresses() {
1251 let pub_key_x1 =
1253 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1254 let pub_key_y1 =
1255 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1256
1257 let pub_key_x2 =
1258 hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1259 let pub_key_y2 =
1260 hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1261
1262 let addr1 = derive_p256_address(&pub_key_x1, &pub_key_y1);
1263 let addr2 = derive_p256_address(&pub_key_x2, &pub_key_y2);
1264
1265 assert_ne!(
1266 addr1, addr2,
1267 "Different keys should produce different addresses"
1268 );
1269 }
1270
1271 #[test]
1272 fn test_tempo_signature_from_bytes_secp256k1() {
1273 use super::SECP256K1_SIGNATURE_LENGTH;
1274
1275 let sig_bytes = vec![0u8; SECP256K1_SIGNATURE_LENGTH];
1277 let result = TempoSignature::from_bytes(&sig_bytes);
1278
1279 assert!(result.is_ok());
1280 if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(_)) = result.unwrap() {
1281 } else {
1283 panic!("Expected Primitive(Secp256k1) variant");
1284 }
1285 }
1286
1287 #[test]
1288 fn test_tempo_signature_from_bytes_p256() {
1289 use super::{P256_SIGNATURE_LENGTH, SIGNATURE_TYPE_P256};
1290
1291 let mut sig_bytes = vec![SIGNATURE_TYPE_P256];
1292 sig_bytes.extend_from_slice(&[0u8; P256_SIGNATURE_LENGTH]);
1293 let result = TempoSignature::from_bytes(&sig_bytes);
1294
1295 assert!(result.is_ok());
1296 if let TempoSignature::Primitive(PrimitiveSignature::P256(_)) = result.unwrap() {
1297 } else {
1299 panic!("Expected Primitive(P256) variant");
1300 }
1301 }
1302
1303 #[test]
1304 fn test_tempo_signature_from_bytes_webauthn() {
1305 use super::SIGNATURE_TYPE_WEBAUTHN;
1306
1307 let mut sig_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1308 sig_bytes.extend_from_slice(&[0u8; 200]); let result = TempoSignature::from_bytes(&sig_bytes);
1310
1311 assert!(result.is_ok());
1312 if let TempoSignature::Primitive(PrimitiveSignature::WebAuthn(_)) = result.unwrap() {
1313 } else {
1315 panic!("Expected Primitive(WebAuthn) variant");
1316 }
1317 }
1318
1319 #[test]
1320 fn test_tempo_signature_from_bytes_validation() {
1321 assert_eq!(
1323 TempoSignature::from_bytes(&[]).unwrap_err(),
1324 "Signature data is empty"
1325 );
1326 assert_eq!(
1327 PrimitiveSignature::from_bytes(&[]).unwrap_err(),
1328 "Signature data is empty"
1329 );
1330
1331 assert_eq!(
1333 TempoSignature::from_bytes(&[0x01]).unwrap_err(),
1334 "Signature data too short: expected type identifier + signature data"
1335 );
1336
1337 let mut bad_p256 = vec![SIGNATURE_TYPE_P256];
1339 bad_p256.extend_from_slice(&[0u8; 100]); assert_eq!(
1341 TempoSignature::from_bytes(&bad_p256).unwrap_err(),
1342 "Invalid P256 signature length"
1343 );
1344
1345 let mut bad_webauthn = vec![SIGNATURE_TYPE_WEBAUTHN];
1347 bad_webauthn.extend_from_slice(&[0u8; 50]); assert_eq!(
1349 TempoSignature::from_bytes(&bad_webauthn).unwrap_err(),
1350 "Invalid WebAuthn signature length"
1351 );
1352
1353 let mut unknown_type = vec![0xFF];
1355 unknown_type.extend_from_slice(&[0u8; 100]);
1356 assert_eq!(
1357 TempoSignature::from_bytes(&unknown_type).unwrap_err(),
1358 "Unknown signature type identifier"
1359 );
1360 }
1361
1362 #[test]
1363 fn test_tempo_signature_roundtrip() {
1364 use super::{
1365 P256_SIGNATURE_LENGTH, SECP256K1_SIGNATURE_LENGTH, SIGNATURE_TYPE_P256,
1366 SIGNATURE_TYPE_WEBAUTHN,
1367 };
1368
1369 let sig1_bytes = vec![1u8; SECP256K1_SIGNATURE_LENGTH];
1371 let sig1 = TempoSignature::from_bytes(&sig1_bytes).unwrap();
1372 let encoded1 = sig1.to_bytes();
1373 assert_eq!(encoded1.len(), SECP256K1_SIGNATURE_LENGTH); let decoded1 = TempoSignature::from_bytes(&encoded1).unwrap();
1376 assert_eq!(sig1, decoded1);
1377
1378 let mut sig2_bytes = vec![SIGNATURE_TYPE_P256];
1380 sig2_bytes.extend_from_slice(&[2u8; P256_SIGNATURE_LENGTH]);
1381 let sig2 = TempoSignature::from_bytes(&sig2_bytes).unwrap();
1382 let encoded2 = sig2.to_bytes();
1383 assert_eq!(encoded2.len(), 1 + P256_SIGNATURE_LENGTH);
1384 let decoded2 = TempoSignature::from_bytes(&encoded2).unwrap();
1386 assert_eq!(sig2, decoded2);
1387
1388 let mut sig3_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1390 sig3_bytes.extend_from_slice(&[3u8; 200]);
1391 let sig3 = TempoSignature::from_bytes(&sig3_bytes).unwrap();
1392 let encoded3 = sig3.to_bytes();
1393 assert_eq!(encoded3.len(), 1 + 200);
1394 let decoded3 = TempoSignature::from_bytes(&encoded3).unwrap();
1396 assert_eq!(sig3, decoded3);
1397 }
1398
1399 #[test]
1400 #[cfg(feature = "serde")]
1401 fn test_tempo_signature_serde_roundtrip() {
1402 let r_bytes = hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
1406 let s_bytes = hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321");
1407 let sig = Signature::new(
1408 alloy_primitives::U256::from_be_slice(&r_bytes),
1409 alloy_primitives::U256::from_be_slice(&s_bytes),
1410 false,
1411 );
1412 let secp256k1_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig));
1413
1414 let json = serde_json::to_string(&secp256k1_sig).unwrap();
1415 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1416 assert_eq!(secp256k1_sig, decoded, "Secp256k1 serde roundtrip failed");
1417
1418 let p256_sig =
1420 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1421 r: B256::from([1u8; 32]),
1422 s: B256::from([2u8; 32]),
1423 pub_key_x: B256::from([3u8; 32]),
1424 pub_key_y: B256::from([4u8; 32]),
1425 pre_hash: true,
1426 }));
1427
1428 let json = serde_json::to_string(&p256_sig).unwrap();
1429 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1430 assert_eq!(p256_sig, decoded, "P256 serde roundtrip failed");
1431
1432 assert!(
1434 json.contains("\"pubKeyX\""),
1435 "Should use camelCase for pubKeyX"
1436 );
1437 assert!(
1438 json.contains("\"pubKeyY\""),
1439 "Should use camelCase for pubKeyY"
1440 );
1441 assert!(
1442 json.contains("\"preHash\""),
1443 "Should use camelCase for preHash"
1444 );
1445
1446 let webauthn_sig =
1448 TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1449 r: B256::from([5u8; 32]),
1450 s: B256::from([6u8; 32]),
1451 pub_key_x: B256::from([7u8; 32]),
1452 pub_key_y: B256::from([8u8; 32]),
1453 webauthn_data: Bytes::from(vec![9u8; 50]),
1454 }));
1455
1456 let json = serde_json::to_string(&webauthn_sig).unwrap();
1457 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1458 assert_eq!(webauthn_sig, decoded, "WebAuthn serde roundtrip failed");
1459
1460 assert!(
1462 json.contains("\"pubKeyX\""),
1463 "Should use camelCase for pubKeyX"
1464 );
1465 assert!(
1466 json.contains("\"pubKeyY\""),
1467 "Should use camelCase for pubKeyY"
1468 );
1469 assert!(
1470 json.contains("\"webauthnData\""),
1471 "Should use camelCase for webauthnData"
1472 );
1473 }
1474
1475 #[test]
1476 fn test_webauthn_flag_validation() {
1477 let tx_hash = B256::ZERO;
1478
1479 let data = build_webauthn_data(0x41, None, &tx_hash); let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1482 assert!(err.contains("AT flag"), "Should reject AT flag");
1483
1484 let data = build_webauthn_data(0x81, Some(&[0xa0]), &tx_hash); let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1487 assert!(err.contains("ED flag"), "Should reject ED flag");
1488
1489 let data = build_webauthn_data(0x01, None, &tx_hash); assert!(
1492 verify_webauthn_data_internal(&data, &tx_hash).is_ok(),
1493 "Should accept valid webauthn data with only UP flag"
1494 );
1495 }
1496
1497 #[test]
1498 fn test_recover_signer_p256() {
1499 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1500 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1501
1502 let sig_hash = B256::from([0xAA; 32]);
1503 let (r, s) = sign_p256_normalized(&signing_key, &sig_hash);
1504
1505 let p256_sig =
1506 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1507 r,
1508 s,
1509 pub_key_x,
1510 pub_key_y,
1511 pre_hash: false,
1512 }));
1513
1514 let recovered = p256_sig.recover_signer(&sig_hash).unwrap();
1515 assert_eq!(
1516 recovered, expected_address,
1517 "P256 recovery should match derived address"
1518 );
1519 }
1520
1521 #[test]
1522 fn test_recover_signer_p256_with_prehash() {
1523 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1524 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1525
1526 let sig_hash = B256::from([0xBB; 32]);
1528 let prehashed = B256::from_slice(Sha256::digest(sig_hash).as_ref());
1529 let (r, s) = sign_p256_normalized(&signing_key, &prehashed);
1530
1531 let p256_sig =
1532 TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1533 r,
1534 s,
1535 pub_key_x,
1536 pub_key_y,
1537 pre_hash: true,
1538 }));
1539
1540 let recovered = p256_sig.recover_signer(&sig_hash).unwrap();
1541 assert_eq!(
1542 recovered, expected_address,
1543 "P256 pre_hash recovery should match"
1544 );
1545 }
1546
1547 #[test]
1548 fn test_recover_signer_webauthn() {
1549 let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1550 let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1551
1552 let tx_hash = B256::from([0xCC; 32]);
1553 let webauthn_data = build_webauthn_data(0x01, None, &tx_hash);
1554
1555 let auth_data = &webauthn_data[..37];
1557 let client_data = &webauthn_data[37..];
1558 let client_data_hash = Sha256::digest(client_data);
1559 let mut hasher = Sha256::new();
1560 hasher.update(auth_data);
1561 hasher.update(client_data_hash);
1562 let message_hash = B256::from_slice(&hasher.finalize());
1563
1564 let (r, s) = sign_p256_normalized(&signing_key, &message_hash);
1565
1566 let webauthn_sig =
1567 TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1568 r,
1569 s,
1570 pub_key_x,
1571 pub_key_y,
1572 webauthn_data: Bytes::from(webauthn_data),
1573 }));
1574
1575 let recovered = webauthn_sig.recover_signer(&tx_hash).unwrap();
1576 assert_eq!(
1577 recovered, expected_address,
1578 "WebAuthn recovery should match derived address"
1579 );
1580 }
1581
1582 #[test]
1583 fn test_recover_signer_keychain_v1() {
1584 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1585
1586 let (signing_key, access_key_address) = generate_secp256k1_keypair();
1587 let user_address = Address::repeat_byte(0xDD);
1588
1589 let sig_hash = B256::from([0x22; 32]);
1591 let inner_sig = sign_hash(&signing_key, &sig_hash);
1592
1593 let keychain_sig = TempoSignature::Keychain(KeychainSignature::new_v1(
1594 user_address,
1595 match inner_sig {
1596 TempoSignature::Primitive(p) => p,
1597 _ => panic!("Expected primitive signature"),
1598 },
1599 ));
1600
1601 let recovered = keychain_sig.recover_signer(&sig_hash).unwrap();
1603 assert_eq!(
1604 recovered, user_address,
1605 "Keychain V1 recovery should return user_address"
1606 );
1607
1608 let keychain = keychain_sig.as_keychain().unwrap();
1610 let key_id = keychain.key_id(&sig_hash).unwrap();
1611 assert_eq!(
1612 key_id, access_key_address,
1613 "key_id should return access key address"
1614 );
1615
1616 assert!(keychain_sig.is_legacy_keychain());
1618 }
1619
1620 #[test]
1621 fn test_recover_signer_keychain_v2() {
1622 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1623
1624 let (signing_key, access_key_address) = generate_secp256k1_keypair();
1625 let user_address = Address::repeat_byte(0xDD);
1626
1627 let sig_hash = B256::from([0x22; 32]);
1629 let mut buf = [0u8; 53]; buf[0] = SIGNATURE_TYPE_KEYCHAIN_V2;
1631 buf[1..33].copy_from_slice(sig_hash.as_slice());
1632 buf[33..].copy_from_slice(user_address.as_slice());
1633 let effective_hash = keccak256(buf);
1634 let inner_sig = sign_hash(&signing_key, &effective_hash);
1635
1636 let keychain_sig = TempoSignature::Keychain(KeychainSignature::new(
1637 user_address,
1638 match inner_sig {
1639 TempoSignature::Primitive(p) => p,
1640 _ => panic!("Expected primitive signature"),
1641 },
1642 ));
1643
1644 let recovered = keychain_sig.recover_signer(&sig_hash).unwrap();
1646 assert_eq!(
1647 recovered, user_address,
1648 "Keychain V2 recovery should return user_address"
1649 );
1650
1651 let keychain = keychain_sig.as_keychain().unwrap();
1653 let key_id = keychain.key_id(&sig_hash).unwrap();
1654 assert_eq!(
1655 key_id, access_key_address,
1656 "key_id should return access key address"
1657 );
1658
1659 assert!(!keychain_sig.is_legacy_keychain());
1661 }
1662
1663 #[test]
1664 fn test_keychain_v2_binds_user_address() {
1665 use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1666
1667 let (signing_key, _access_key_address) = generate_secp256k1_keypair();
1668 let user_a = Address::repeat_byte(0xAA);
1669 let user_b = Address::repeat_byte(0xBB);
1670
1671 let sig_hash = B256::from([0x22; 32]);
1673 let mut buf = [0u8; 52];
1674 buf[..32].copy_from_slice(sig_hash.as_slice());
1675 buf[32..].copy_from_slice(user_a.as_slice());
1676 let effective_hash = keccak256(buf);
1677 let inner_sig = sign_hash(&signing_key, &effective_hash);
1678
1679 let inner_primitive = match inner_sig {
1680 TempoSignature::Primitive(p) => p,
1681 _ => panic!("Expected primitive signature"),
1682 };
1683
1684 let sig_a =
1686 TempoSignature::Keychain(KeychainSignature::new(user_a, inner_primitive.clone()));
1687 let recovered_a = sig_a.recover_signer(&sig_hash).unwrap();
1688 assert_eq!(recovered_a, user_a);
1689
1690 let sig_b = TempoSignature::Keychain(KeychainSignature::new(user_b, inner_primitive));
1693 let recovered_b = sig_b.recover_signer(&sig_hash).unwrap();
1694 assert_eq!(
1695 recovered_b, user_b,
1696 "recover_signer returns the claimed user_address"
1697 );
1698
1699 let key_id_a = sig_a.as_keychain().unwrap().key_id(&sig_hash).unwrap();
1701 let key_id_b = sig_b.as_keychain().unwrap().key_id(&sig_hash).unwrap();
1702 assert_ne!(
1703 key_id_a, key_id_b,
1704 "V2 should recover different key_ids for different user_addresses"
1705 );
1706 }
1707
1708 #[test]
1709 fn test_signing_hash_properties() {
1710 let hash_a = B256::from([0x11; 32]);
1711 let hash_b = B256::from([0x22; 32]);
1712 let addr_a = Address::repeat_byte(0xAA);
1713 let addr_b = Address::repeat_byte(0xBB);
1714
1715 assert_ne!(
1717 KeychainSignature::signing_hash(hash_a, addr_a),
1718 KeychainSignature::signing_hash(hash_a, addr_b),
1719 );
1720
1721 assert_ne!(
1723 KeychainSignature::signing_hash(hash_a, addr_a),
1724 KeychainSignature::signing_hash(hash_b, addr_a),
1725 );
1726
1727 assert_eq!(
1729 KeychainSignature::signing_hash(hash_a, addr_a),
1730 KeychainSignature::signing_hash(hash_a, addr_a),
1731 );
1732 }
1733
1734 #[test]
1735 fn test_webauthn_rejects_challenge_injection() {
1736 let (tx_hash, attack_hash) = (B256::from([0xAA; 32]), B256::from([0xFF; 32]));
1737 let (challenge, attack_challenge) = (
1738 URL_SAFE_NO_PAD.encode(tx_hash.as_slice()),
1739 URL_SAFE_NO_PAD.encode(attack_hash.as_slice()),
1740 );
1741
1742 let valid_payload = format!(r#"{{"type":"webauthn.get","challenge":"{challenge}"}}"#);
1744
1745 let mut auth_data = vec![0u8; 37];
1746 auth_data[32] = 0x01;
1747 let mut webauthn_data = auth_data;
1748 webauthn_data.extend_from_slice(valid_payload.as_bytes());
1749
1750 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1751 assert!(result.is_ok());
1752
1753 let attack_variants = [
1755 format!(
1756 r#"{{"type":"webauthn.get","challenge":"{attack_challenge}","extra":{{"challenge":"{challenge}"}}}}"#
1757 ),
1758 format!(
1759 r#"{{"type":"webauthn.get","data":[{{"challenge":"{challenge}"}}],"challenge":"{attack_challenge}"}}"#
1760 ),
1761 ];
1762
1763 for (i, attack_json) in attack_variants.iter().enumerate() {
1764 let mut auth_data = vec![0u8; 37];
1765 auth_data[32] = 0x01;
1766 let mut webauthn_data = auth_data;
1767 webauthn_data.extend_from_slice(attack_json.as_bytes());
1768
1769 let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1770 assert!(
1771 result.is_err(),
1772 "Attack variant {i} should be rejected: {attack_json}"
1773 );
1774 }
1775 }
1776
1777 #[test]
1778 fn test_keychain_signature_eq_same() {
1779 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1780 let addr = Address::repeat_byte(0x01);
1781 let a = KeychainSignature::new(addr, sig.clone());
1782 let b = KeychainSignature::new(addr, sig);
1783 assert_eq!(a, b);
1784 }
1785
1786 #[test]
1787 fn test_keychain_signature_eq_different_address() {
1788 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1789 let a = KeychainSignature::new(Address::repeat_byte(0x01), sig.clone());
1790 let b = KeychainSignature::new(Address::repeat_byte(0x02), sig);
1791 assert_ne!(a, b);
1792 }
1793
1794 #[test]
1795 fn test_keychain_signature_eq_different_signature() {
1796 let addr = Address::repeat_byte(0x01);
1797 let sig_a = PrimitiveSignature::Secp256k1(Signature::test_signature());
1798 let sig_b = PrimitiveSignature::P256(P256SignatureWithPreHash {
1799 r: B256::from([1u8; 32]),
1800 s: B256::from([2u8; 32]),
1801 pub_key_x: B256::from([3u8; 32]),
1802 pub_key_y: B256::from([4u8; 32]),
1803 pre_hash: false,
1804 });
1805 let a = KeychainSignature::new(addr, sig_a);
1806 let b = KeychainSignature::new(addr, sig_b);
1807 assert_ne!(a, b);
1808 }
1809
1810 #[test]
1811 fn test_keychain_signature_hash_differs_for_different_sigs() {
1812 use std::{
1813 collections::hash_map::DefaultHasher,
1814 hash::{Hash, Hasher},
1815 };
1816
1817 let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1818 let a = KeychainSignature::new(Address::repeat_byte(0x01), sig.clone());
1819 let b = KeychainSignature::new(Address::repeat_byte(0x02), sig.clone());
1820 let c = KeychainSignature::new(Address::repeat_byte(0x01), sig);
1821
1822 let hash = |k: &KeychainSignature| {
1823 let mut h = DefaultHasher::new();
1824 k.hash(&mut h);
1825 h.finish()
1826 };
1827
1828 assert_ne!(
1829 hash(&a),
1830 hash(&b),
1831 "different address should produce different hash"
1832 );
1833 assert_eq!(hash(&a), hash(&c), "same fields should produce same hash");
1834 }
1835
1836 #[test]
1837 fn test_primitive_signature_from_bytes_one_byte() {
1838 let result = PrimitiveSignature::from_bytes(&[0x01]);
1839 assert!(result.is_err());
1840 assert!(result.unwrap_err().contains("too short"));
1841 }
1842
1843 #[test]
1844 fn test_tempo_signature_keychain_too_short_for_address() {
1845 for type_byte in [SIGNATURE_TYPE_KEYCHAIN, SIGNATURE_TYPE_KEYCHAIN_V2] {
1846 let mut data = vec![type_byte];
1847 data.extend_from_slice(&[0u8; 19]);
1848 let result = TempoSignature::from_bytes(&data);
1849 assert!(result.is_err());
1850 assert!(result.unwrap_err().contains("too short"));
1851 }
1852 }
1853
1854 #[test]
1855 fn test_tempo_signature_keychain_exactly_20_bytes_inner_empty() {
1856 let mut data = vec![SIGNATURE_TYPE_KEYCHAIN];
1857 data.extend_from_slice(&[0u8; 20]);
1858 let result = TempoSignature::from_bytes(&data);
1859 assert!(result.is_err());
1860 }
1861
1862 #[test]
1863 fn test_is_keychain_returns_false_for_primitive() {
1864 let sig =
1865 TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
1866 assert!(!sig.is_keychain());
1867 }
1868
1869 #[test]
1870 fn test_is_keychain_returns_true_for_keychain() {
1871 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
1872 let sig = TempoSignature::Keychain(KeychainSignature::new(Address::ZERO, inner));
1873 assert!(sig.is_keychain());
1874 }
1875
1876 #[test]
1877 fn test_keychain_v1_v2_bytes_roundtrip_and_wire_format() {
1878 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
1879 let user = Address::repeat_byte(0xAA);
1880
1881 let v1 = TempoSignature::Keychain(KeychainSignature::new_v1(user, inner.clone()));
1883 let v1_bytes = v1.to_bytes();
1884 assert_eq!(v1_bytes[0], SIGNATURE_TYPE_KEYCHAIN);
1885 let v1_decoded = TempoSignature::from_bytes(&v1_bytes).unwrap();
1886 assert_eq!(v1, v1_decoded);
1887 assert!(v1_decoded.is_legacy_keychain());
1888
1889 let v2 = TempoSignature::Keychain(KeychainSignature::new(user, inner));
1891 let v2_bytes = v2.to_bytes();
1892 assert_eq!(v2_bytes[0], SIGNATURE_TYPE_KEYCHAIN_V2);
1893 let v2_decoded = TempoSignature::from_bytes(&v2_bytes).unwrap();
1894 assert_eq!(v2, v2_decoded);
1895 assert!(!v2_decoded.is_legacy_keychain());
1896
1897 assert_ne!(v1, v2);
1899 }
1900
1901 #[test]
1902 #[cfg(feature = "serde")]
1903 fn test_keychain_serde_roundtrip_and_backward_compat() {
1904 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
1905 let user = Address::repeat_byte(0xBB);
1906
1907 let v2 = TempoSignature::Keychain(KeychainSignature::new(user, inner.clone()));
1909 let json = serde_json::to_string(&v2).unwrap();
1910 let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1911 assert_eq!(v2, decoded);
1912 assert!(!decoded.is_legacy_keychain());
1913
1914 let v1 = TempoSignature::Keychain(KeychainSignature::new_v1(user, inner));
1916 let json_v1 = serde_json::to_string(&v1).unwrap();
1917 let decoded_v1: TempoSignature = serde_json::from_str(&json_v1).unwrap();
1918 assert_eq!(v1, decoded_v1);
1919 assert!(decoded_v1.is_legacy_keychain());
1920
1921 let json_no_version = json_v1.replace(r#","version":"v1""#, "");
1923 assert!(
1924 !json_no_version.contains("version"),
1925 "version field should be stripped"
1926 );
1927 let decoded_no_version: TempoSignature = serde_json::from_str(&json_no_version).unwrap();
1928 assert!(decoded_no_version.is_legacy_keychain());
1929 }
1930
1931 #[test]
1932 fn test_keychain_rlp_roundtrip_preserves_version() {
1933 use alloy_rlp::Decodable;
1934
1935 let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
1936 let user = Address::repeat_byte(0xCC);
1937
1938 for (sig, expect_legacy) in [
1939 (
1940 TempoSignature::Keychain(KeychainSignature::new_v1(user, inner.clone())),
1941 true,
1942 ),
1943 (
1944 TempoSignature::Keychain(KeychainSignature::new(user, inner)),
1945 false,
1946 ),
1947 ] {
1948 let mut buf = Vec::new();
1949 alloy_rlp::Encodable::encode(&sig, &mut buf);
1950 let decoded = TempoSignature::decode(&mut buf.as_slice()).unwrap();
1951 assert_eq!(sig, decoded);
1952 assert_eq!(decoded.is_legacy_keychain(), expect_legacy);
1953 }
1954 }
1955}