Skip to main content

tempo_primitives/transaction/
tt_signature.rs

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
18/// The P256 (secp256r1/prime256v1) curve order n.
19pub const P256_ORDER: U256 =
20    uint!(0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551_U256);
21
22/// Half of the P256 curve order (n/2).
23///
24/// For signatures to be valid, the s value must be less than or equal to n/2
25/// (low-s requirement). This prevents signature malleability where (r, s) and
26/// (r, n-s) are both valid signatures for the same message.
27pub const P256N_HALF: U256 =
28    uint!(0x7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8_U256);
29
30/// Normalize P256 signature s value to low-s form.
31///
32/// For any ECDSA signature (r, s), both (r, s) and (r, n-s) are valid.
33/// To prevent signature malleability, we require s <= n/2.
34/// If s > n/2, we replace it with n - s.
35///
36/// This function should be called by all P256 signing code before creating
37/// a signature, as the p256 crate does not guarantee low-s signatures.
38pub 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
44/// Signature type identifiers
45/// Note: Secp256k1 has no identifier - detected by length (65 bytes)
46pub 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
51// Minimum authenticatorData is 37 bytes (32 rpIdHash + 1 flags + 4 signCount)
52const MIN_AUTH_DATA_LEN: usize = 37;
53
54/// WebAuthn authenticator data flags (byte 32)
55/// ref: <https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data>
56const UP: u8 = 0x01; // User Presence (bit 0)
57const UV: u8 = 0x04; // User Verified (bit 2)
58const AT: u8 = 0x40; // Attested credential data (bit 6)
59const ED: u8 = 0x80; // Extension data present (bit 7)
60
61/// P256 signature with pre-hash flag
62#[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/// WebAuthn signature with authenticator data
77#[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    /// authenticatorData || clientDataJSON (variable length)
89    pub webauthn_data: Bytes,
90}
91
92/// Primitive signature types that can be used standalone or within a Keychain signature.
93/// This enum contains only the base signature types: Secp256k1, P256, and WebAuthn.
94/// It does NOT support Keychain signatures to prevent recursion.
95///
96/// Note: This enum uses custom RLP encoding via `to_bytes()` and does NOT derive Compact.
97/// The Compact encoding is handled at the parent struct level (e.g., KeyAuthorization).
98#[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    /// Standard secp256k1 ECDSA signature (65 bytes: r, s, v)
108    Secp256k1(Signature),
109
110    /// P256 signature with embedded public key (129 bytes)
111    P256(P256SignatureWithPreHash),
112
113    /// WebAuthn signature with variable-length authenticator data
114    WebAuthn(WebAuthnSignature),
115}
116
117impl PrimitiveSignature {
118    /// Parse signature from bytes with backward compatibility
119    ///
120    /// For backward compatibility with existing secp256k1 signatures:
121    /// - If length is 65 bytes: treat as secp256k1 signature (no type identifier)
122    /// - Otherwise: first byte is the signature type identifier
123    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        // Backward compatibility: exactly 65 bytes means secp256k1 without type identifier
129        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        // For all other lengths, first byte is the type identifier
136        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    /// Encode signature to bytes
175    ///
176    /// For backward compatibility:
177    /// - Secp256k1: encoded WITHOUT type identifier (65 bytes)
178    /// - P256/WebAuthn: encoded WITH type identifier prefix
179    pub fn to_bytes(&self) -> Bytes {
180        match self {
181            Self::Secp256k1(sig) => {
182                // Backward compatibility: no type identifier for secp256k1
183                // Ensure exactly 65 bytes by using a fixed-size buffer
184                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    /// Get the length of the encoded signature in bytes
216    ///
217    /// For backward compatibility:
218    /// - Secp256k1: 65 bytes (no type identifier)
219    /// - P256/WebAuthn: includes 1-byte type identifier prefix
220    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    /// Get signature type
229    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    /// Get the in-memory size of the signature
238    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    /// Recover the signer address from the signature
247    ///
248    /// This function verifies the signature and extracts the address based on signature type:
249    /// - secp256k1: Uses standard ecrecover (signature verification + address recovery)
250    /// - P256: Verifies P256 signature then derives address from public key
251    /// - WebAuthn: Parses WebAuthn data, verifies P256 signature, derives address
252    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                // Standard secp256k1 recovery using alloy's built-in methods
259                // This simultaneously verifies the signature AND recovers the address
260                alloy_consensus::crypto::secp256k1::recover_signer(sig, *sig_hash)
261            }
262            Self::P256(p256_sig) => {
263                // Prepare message hash for verification
264                let message_hash = if p256_sig.pre_hash {
265                    // Some P256 implementations (like Web Crypto) require pre-hashing
266                    B256::from_slice(Sha256::digest(sig_hash).as_ref())
267                } else {
268                    *sig_hash
269                };
270
271                // Verify P256 signature cryptographically
272                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                // Derive and return address
282                Ok(derive_p256_address(
283                    &p256_sig.pub_key_x,
284                    &p256_sig.pub_key_y,
285                ))
286            }
287            Self::WebAuthn(webauthn_sig) => {
288                // Parse and verify WebAuthn data, compute challenge hash
289                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 over the computed message hash
294                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                // Derive and return address
304                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        // Delegate to Bytes::to_compact which handles variable-length encoding
345        bytes.to_compact(buf)
346    }
347
348    fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
349        // Delegate to Bytes::from_compact which handles variable-length decoding
350        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/// Keychain signature version.
358///
359/// Determines how the signature hash is computed for the inner signature.
360#[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    /// Legacy (V1): inner signature signs `sig_hash` directly.
366    /// Deprecated at T1C.
367    /// TODO(tanishk): change default to V2 after T1C
368    #[default]
369    V1,
370    /// V2: inner signature signs `keccak256(0x04 || sig_hash || user_address)`.
371    /// Binds the signature to the specific user account with a domain separator.
372    V2,
373}
374
375/// Keychain version validation error.
376///
377/// Returned by [`TempoSignature::validate_version`] when a keychain
378/// signature's version is incompatible with the current hardfork.
379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380pub enum KeychainVersionError {
381    /// Legacy V1 keychain signature used after T1C activation (permanently invalid).
382    LegacyPostT1C,
383    /// V2 keychain signature used before T1C activation (not yet valid).
384    V2BeforeActivation,
385}
386
387/// Keychain signature wrapping another signature with a user address.
388/// This allows an access key to sign on behalf of a root account.
389///
390/// No `Compact` impl — always wrapped in [`TempoSignature`] whose `Compact` delegates
391/// to `to_bytes()`/`from_bytes()` which encodes the version via the wire type byte
392/// (`0x03` = V1, `0x04` = V2).
393///
394/// Format (V1): 0x03 || user_address (20 bytes) || inner_signature
395/// Format (V2): 0x04 || user_address (20 bytes) || inner_signature
396///
397/// The user_address is the root account this transaction is being executed for.
398/// The inner signature proves an authorized access key signed the transaction.
399/// The handler validates that user_address has authorized the access key in the KeyChain precompile.
400#[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    /// Root account address that this transaction is being executed for
405    pub user_address: Address,
406    /// The actual signature from the access key (can be Secp256k1, P256, or WebAuthn, but NOT another Keychain)
407    pub signature: PrimitiveSignature,
408    /// Keychain signature version (V1 = legacy, V2 = includes user_address in sig hash)
409    #[cfg_attr(feature = "serde", serde(default))]
410    pub version: KeychainVersion,
411    /// Cached access key ID recovered from the inner signature.
412    /// This is an implementation detail - use `key_id()` to access.
413    /// Uses OnceLock for thread-safe interior mutability.
414    /// Note: Excluded from PartialEq, Eq, Hash, and Compact as it's a cache.
415    #[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    /// Create a new V2 KeychainSignature (recommended).
428    ///
429    /// V2 signatures include the user_address in the signature hash.
430    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    /// Create a legacy V1 KeychainSignature.
440    ///
441    /// V1 signatures do NOT include the user_address in the signature hash
442    /// and are deprecated at the T1C hardfork.
443    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    /// Compute the effective signature hash for key recovery.
453    ///
454    /// - V1: returns `sig_hash` directly (legacy, deprecated)
455    /// - V2: returns `keccak256(0x04 || sig_hash || user_address)`
456    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    /// Get the access key ID for Keychain signatures.
464    ///
465    /// For Keychain signatures, this returns the access key address that signed the transaction.
466    /// The key_id is recovered from the inner signature on first access and cached for
467    /// subsequent calls. Returns None for non-Keychain signatures.
468    ///
469    /// This follows the pattern used in alloy for lazy hash computation.
470    pub fn key_id(
471        &self,
472        sig_hash: &B256,
473    ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
474        // Check if already cached
475        if let Some(cached) = self.cached_key_id.get() {
476            return Ok(*cached);
477        }
478
479        // Not cached - recover and cache
480        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    /// Returns true if this is a legacy V1 keychain signature.
488    pub fn is_legacy(&self) -> bool {
489        self.version == KeychainVersion::V1
490    }
491
492    /// Compute the hash that an access key should sign for a V2 keychain transaction.
493    ///
494    /// Returns `keccak256(0x04 || sig_hash || user_address)`.
495    /// The `0x04` domain separator ([`SIGNATURE_TYPE_KEYCHAIN_V2`]) prevents
496    /// cross-scheme signature confusion, following the same pattern as
497    /// EIP-7702 (`0x05`) and Tempo fee-payer signatures (`0x78`).
498    pub fn signing_hash(sig_hash: B256, user_address: Address) -> B256 {
499        let mut buf = [0u8; 53]; // 1 + 32 + 20
500        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
507// Manual implementations of PartialEq, Eq, and Hash that exclude cached_key_id
508// since it's just a cache and doesn't affect the logical equality of signatures
509impl 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// Manual Arbitrary implementation that excludes cached_key_id (cache field)
528#[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(), // Always start with empty cache
536        })
537    }
538}
539
540/// AA transaction signature supporting multiple signature schemes
541///
542/// Note: Uses custom Compact implementation that delegates to `to_bytes()` / `from_bytes()`.
543#[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 signature types: Secp256k1, P256, or WebAuthn
550    Primitive(PrimitiveSignature),
551
552    /// Keychain signature - wraps another signature with a key identifier
553    /// Format: key_id (20 bytes) + inner signature
554    /// IMP: The inner signature MUST NOT be another Keychain (validated at runtime)
555    /// Note: Recursion is prevented by KeychainSignature's custom Arbitrary impl
556    Keychain(KeychainSignature),
557}
558
559impl TempoSignature {
560    /// Parse signature from bytes with backward compatibility
561    ///
562    /// For backward compatibility with existing secp256k1 signatures:
563    /// - If length is 65 bytes: treat as secp256k1 signature (no type identifier)
564    /// - Otherwise: first byte is the signature type identifier
565    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        // Check if this is a Keychain signature (type identifier 0x03 or 0x04)
571        // We need to handle this specially before delegating to PrimitiveSignature
572        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            // Keychain format: user_address (20 bytes) || inner_signature
584            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            // Parse inner signature using PrimitiveSignature (which doesn't support Keychain)
592            // This automatically prevents recursive keychain signatures at compile time
593            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        // For all non-Keychain signatures, delegate to PrimitiveSignature
604        let primitive = PrimitiveSignature::from_bytes(data)?;
605        Ok(Self::Primitive(primitive))
606    }
607
608    /// Encode signature to bytes
609    ///
610    /// For backward compatibility:
611    /// - Secp256k1: encoded WITHOUT type identifier (65 bytes)
612    /// - P256/WebAuthn: encoded WITH type identifier prefix
613    pub fn to_bytes(&self) -> Bytes {
614        match self {
615            Self::Primitive(primitive_sig) => primitive_sig.to_bytes(),
616            Self::Keychain(keychain_sig) => {
617                // Format: type_byte | user_address (20 bytes) | inner_signature
618                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    /// Get the length of the encoded signature in bytes
633    ///
634    /// For backward compatibility:
635    /// - Secp256k1: 65 bytes (no type identifier)
636    /// - P256/WebAuthn: includes 1-byte type identifier prefix
637    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    /// Get signature type
645    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    /// Get the in-memory size of the signature
653    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    /// Recover the signer address from the signature
661    ///
662    /// This function verifies the signature and extracts the address based on signature type:
663    /// - secp256k1: Uses standard ecrecover (signature verification + address recovery)
664    /// - P256: Verifies P256 signature then derives address from public key
665    /// - WebAuthn: Parses WebAuthn data, verifies P256 signature, derives address
666    /// - Keychain: Validates inner signature and returns user_address
667    ///
668    /// For Keychain signatures, this performs full validation of the inner signature.
669    /// The access key address is cached in the KeychainSignature for later use.
670    /// Note: This pattern has a big footgun, that someone using recover_signer, cannot assume
671    /// that the signature is valid for the keychain. They also need to check the access key is authorized
672    /// in the keychain precompile.
673    /// We cannot check this here, as we don't have access to the keychain precompile.
674    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                // Ensure validity of the keychain signature and cache the key id
682                keychain_sig.key_id(sig_hash)?;
683
684                // Return the user_address - the root account this transaction is for
685                Ok(keychain_sig.user_address)
686            }
687        }
688    }
689
690    /// Check if this is a Keychain signature
691    pub fn is_keychain(&self) -> bool {
692        matches!(self, Self::Keychain(_))
693    }
694
695    /// Check if this is a legacy V1 Keychain signature (deprecated at T1C).
696    pub fn is_legacy_keychain(&self) -> bool {
697        matches!(self, Self::Keychain(k) if k.is_legacy())
698    }
699
700    /// Check if this is a V2 Keychain signature.
701    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    /// Validates keychain signature version compatibility with the current hardfork.
712    ///
713    /// - Post-T1C: legacy V1 keychain signatures are rejected.
714    /// - Pre-T1C: V2 keychain signatures are rejected to prevent chain splits.
715    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    /// Get the Keychain signature if this is a Keychain signature
726    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        // Delegate to Bytes::to_compact which handles variable-length encoding
766        bytes.to_compact(buf)
767    }
768
769    fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
770        // Delegate to Bytes::from_compact which handles variable-length decoding
771        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
784// ============================================================================
785// Helper Functions for Signature Verification
786// ============================================================================
787
788/// Derives a P256 address from public key coordinates
789pub 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    // Take last 20 bytes as address
793    Address::from_slice(&hash[12..])
794}
795
796/// Verifies a P256 signature using the provided components
797///
798/// This performs actual cryptographic verification of the P256 signature
799/// according to the spec. Called during `recover_signer()` to ensure only
800/// valid signatures enter the mempool.
801///
802/// Includes a high-s value check to prevent signature malleability. For any
803/// ECDSA signature (r, s), a second valid signature (r, n-s) exists. By
804/// requiring s <= n/2 (the "low-s" requirement), we ensure only one canonical
805/// form is accepted, preventing transaction hash malleability attacks.
806fn 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    // High-s value check: reject signatures where s > n/2 to prevent malleability
814    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    // Parse public key from affine coordinates
820    let encoded_point = EncodedPoint::from_affine_coordinates(
821        pub_key_x.into(),
822        pub_key_y.into(),
823        false, // Not compressed
824    );
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    // Verify signature
833    verifying_key
834        .verify_prehash(message_hash.as_slice(), &signature)
835        .map_err(|_| "P256 signature verification failed")
836}
837
838/// Minimal struct to deserialize only the fields we need from clientDataJSON.
839/// serde_json will ignore unknown fields and only parse `type` and `challenge`.
840#[derive(serde::Deserialize)]
841struct ClientDataJson<'a> {
842    #[serde(rename = "type")]
843    type_field: &'a str,
844    challenge: &'a str,
845}
846
847/// Parses and validates WebAuthn data, returning the message hash for P256 verification.
848/// ref: <https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data>
849///
850/// 1. Parses authenticatorData and clientDataJSON
851/// 2. Validates authenticatorData (min 37 bytes, UP flag set)
852/// 3. Validates clientDataJSON (type="webauthn.get", challenge matches tx_hash)
853/// 4. Computes message hash = sha256(authenticatorData || sha256(clientDataJSON))
854fn verify_webauthn_data_internal(
855    webauthn_data: &[u8],
856    tx_hash: &B256,
857) -> Result<B256, &'static str> {
858    // Ensure that we have clientDataJSON after authenticatorData
859    if webauthn_data.len() < MIN_AUTH_DATA_LEN + 32 {
860        return Err("WebAuthn data too short");
861    }
862
863    // Check flags (byte 32)
864    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    // UP or UV flag MUST be set (UV implies user presence per WebAuthn spec)
868    if up_flag == 0 && uv_flag == 0 {
869        return Err("neither UP, nor UV flag set");
870    }
871
872    // AT flag must NOT be set for assertion signatures (`webauthn.get`)
873    if at_flag != 0 {
874        return Err("AT flag must not be set for assertion signatures");
875    }
876
877    // Determine authenticatorData length
878    let auth_data_len = if ed_flag == 0 {
879        // If ED flag is not set, exactly 37 bytes (no extensions)
880        MIN_AUTH_DATA_LEN
881    } else {
882        // ED flag must NOT be set, as Tempo AA doesn't support extensions
883        // NOTE: If we ever want to support extensions, we will have to parse CBOR data
884        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    // Parse clientDataJSON (only extracts type and challenge fields)
891    // NOTE: Size is already bounded by MAX_WEBAUTHN_SIGNATURE_LENGTH (2KB) at signature parsing
892    let client_data: ClientDataJson<'_> =
893        serde_json::from_slice(client_data_json).map_err(|_| "clientDataJSON is not valid JSON")?;
894
895    // Validate type field
896    if client_data.type_field != "webauthn.get" {
897        return Err("clientDataJSON type must be webauthn.get");
898    }
899
900    // Validate challenge matches tx_hash (Base64URL encoded)
901    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    // Compute message hash according to spec:
906    // messageHash = sha256(authenticatorData || sha256(clientDataJSON))
907    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")]
918/// Helper function to serialize a [`OnceLock`] as an [`Option`] if it's initialized.
919fn 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    /// Generate P256 keypair, return (signing_key, pub_key_x, pub_key_y)
937    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    /// Sign a message hash with P256, normalize s, return (r, s)
947    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    /// Build webauthn data with given flags and optional extension bytes
957    fn build_webauthn_data(flags: u8, extension: Option<&[u8]>, tx_hash: &B256) -> Vec<u8> {
958        let mut data = vec![0u8; 32]; // rpIdHash
959        data.push(flags);
960        data.extend_from_slice(&[0u8; 4]); // signCount
961        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        // s < P256N_HALF → unchanged
974        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        // s == P256N_HALF → unchanged
983        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        // s == P256N_HALF + 1 → normalized to P256_ORDER - s
991        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        // s == P256_ORDER - 1 → normalized to 1
1000        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        // Invalid public key should fail
1012        let r = [0u8; 32];
1013        let s = [0u8; 32];
1014        let pub_key_x = [0u8; 32]; // Invalid: point not on curve
1015        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        // Use invalid signature (all zeros)
1027        let r = [0u8; 32];
1028        let s = [0u8; 32];
1029        let message_hash = B256::ZERO;
1030
1031        // Invalid signature (all zeros) should fail
1032        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        // Sign and get raw (non-normalized) signature
1070        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        // Convert s to U256 and compute n - s (the high-s equivalent)
1077        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        // Depending on which s was originally produced, either original or high-s
1082        // should be rejected
1083        let s_is_low = s_value <= P256N_HALF;
1084        if s_is_low {
1085            // Original s is low, so high-s version should be rejected
1086            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            // Original s was already high, so the computed "high_s" is actually low
1100            // This means the original should fail
1101            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        // WebAuthn data must be at least 37 bytes (authenticatorData minimum)
1118        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        // Create valid authenticatorData without UV nor UP flag
1132        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        // Create valid authenticatorData with UV flag
1142        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        // Create valid authenticatorData with UP flag
1153        let mut auth_data = vec![0u8; 37];
1154        auth_data[32] = 0x01; // flags byte with UP flag set
1155
1156        // Add clientDataJSON with wrong type
1157        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        // Create valid authenticatorData with UP flag
1174        let mut auth_data = vec![0u8; 37];
1175        auth_data[32] = 0x01; // flags byte with UP flag set
1176
1177        // Add clientDataJSON with wrong challenge
1178        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); // UP flag
1197
1198        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        // Verify the computed message hash is correct
1205        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        // Should be deterministic
1229        assert_eq!(addr1, addr2);
1230
1231        // Should not be zero address
1232        assert_ne!(addr1, Address::ZERO);
1233    }
1234
1235    #[test]
1236    fn test_p256_address_derivation_deterministic() {
1237        // Test that address derivation is deterministic
1238        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        // Different keys should produce different addresses
1252        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        // Secp256k1 signatures are detected by length (65 bytes), no type identifier
1276        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            // Expected
1282        } 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            // Expected
1298        } 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]); // 200 bytes of WebAuthn data
1309        let result = TempoSignature::from_bytes(&sig_bytes);
1310
1311        assert!(result.is_ok());
1312        if let TempoSignature::Primitive(PrimitiveSignature::WebAuthn(_)) = result.unwrap() {
1313            // Expected
1314        } else {
1315            panic!("Expected Primitive(WebAuthn) variant");
1316        }
1317    }
1318
1319    #[test]
1320    fn test_tempo_signature_from_bytes_validation() {
1321        // Empty input
1322        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        // Too short (1 byte, not secp256k1 length)
1332        assert_eq!(
1333            TempoSignature::from_bytes(&[0x01]).unwrap_err(),
1334            "Signature data too short: expected type identifier + signature data"
1335        );
1336
1337        // Wrong length for P256 (should be 129 bytes after type byte)
1338        let mut bad_p256 = vec![SIGNATURE_TYPE_P256];
1339        bad_p256.extend_from_slice(&[0u8; 100]); // wrong length
1340        assert_eq!(
1341            TempoSignature::from_bytes(&bad_p256).unwrap_err(),
1342            "Invalid P256 signature length"
1343        );
1344
1345        // Wrong length for WebAuthn (too short, < 128 bytes after type byte)
1346        let mut bad_webauthn = vec![SIGNATURE_TYPE_WEBAUTHN];
1347        bad_webauthn.extend_from_slice(&[0u8; 50]); // too short
1348        assert_eq!(
1349            TempoSignature::from_bytes(&bad_webauthn).unwrap_err(),
1350            "Invalid WebAuthn signature length"
1351        );
1352
1353        // Invalid type identifier
1354        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        // Test secp256k1 (no type identifier, detected by 65-byte length)
1370        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); // No type identifier
1374        // Verify roundtrip
1375        let decoded1 = TempoSignature::from_bytes(&encoded1).unwrap();
1376        assert_eq!(sig1, decoded1);
1377
1378        // Test P256
1379        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        // Verify roundtrip
1385        let decoded2 = TempoSignature::from_bytes(&encoded2).unwrap();
1386        assert_eq!(sig2, decoded2);
1387
1388        // Test WebAuthn
1389        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        // Verify roundtrip
1395        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        // Test serde roundtrip for all signature types
1403
1404        // Test Secp256k1
1405        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        // Test P256
1419        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        // Verify camelCase naming
1433        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        // Test WebAuthn
1447        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        // Verify camelCase naming
1461        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        // AT flag must be rejected for assertion signatures
1480        let data = build_webauthn_data(0x41, None, &tx_hash); // UP + AT
1481        let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1482        assert!(err.contains("AT flag"), "Should reject AT flag");
1483
1484        // ED flag must be rejected, as extensions are not supported
1485        let data = build_webauthn_data(0x81, Some(&[0xa0]), &tx_hash); // UP + ED, empty map
1486        let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1487        assert!(err.contains("ED flag"), "Should reject ED flag");
1488
1489        // Valid with only UP flag set
1490        let data = build_webauthn_data(0x01, None, &tx_hash); // UP only
1491        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        // For pre_hash=true, signature is over sha256(sig_hash)
1527        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        // Compute message hash: sha256(authData || sha256(clientDataJSON))
1556        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        // V1: inner signature signs sig_hash directly
1590        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        // recover_signer returns user_address
1602        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        // key_id should be cached and return access key address
1609        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        // V1 should be legacy
1617        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        // V2: inner signature signs keccak256(0x04 || sig_hash || user_address)
1628        let sig_hash = B256::from([0x22; 32]);
1629        let mut buf = [0u8; 53]; // 1 + 32 + 20
1630        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        // recover_signer returns user_address
1645        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        // key_id should be cached and return access key address
1652        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        // V2 should NOT be legacy
1660        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        // Sign for user_a with V2
1672        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        // Valid for user_a
1685        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        // Same inner signature but for user_b — key_id will differ
1691        // because user_address is part of the signed hash
1692        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        // But the key_id recovered under user_b will be a garbage address (not the real access key)
1700        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        // Different addresses produce different signing hashes
1716        assert_ne!(
1717            KeychainSignature::signing_hash(hash_a, addr_a),
1718            KeychainSignature::signing_hash(hash_a, addr_b),
1719        );
1720
1721        // Different tx hashes produce different signing hashes
1722        assert_ne!(
1723            KeychainSignature::signing_hash(hash_a, addr_a),
1724            KeychainSignature::signing_hash(hash_b, addr_a),
1725        );
1726
1727        // Deterministic: same inputs yield same output
1728        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        // Ensure that the happy path works
1743        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        // Ensure that malicious payloads cannot pass validation
1754        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        // V1 round-trips and uses 0x03 wire byte
1882        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        // V2 round-trips and uses 0x04 wire byte
1890        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        // V1 and V2 with same inner sig are NOT equal
1898        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        // V2 serde roundtrip preserves version
1908        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        // V1 serde roundtrip preserves version
1915        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        // Backward compat: JSON without "version" field deserializes as V1
1922        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}