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 sha2::{Digest, Sha256};
8
9// Always mark `p256` as used to avoid `unused_crate_dependencies` warnings in `std` builds.
10use p256 as _;
11
12#[cfg(not(feature = "std"))]
13use once_cell::race::OnceBox as OnceLock;
14#[cfg(feature = "std")]
15use std::sync::OnceLock;
16
17/// The P256 (secp256r1/prime256v1) curve order n.
18pub const P256_ORDER: U256 =
19    uint!(0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551_U256);
20
21/// Half of the P256 curve order (n/2).
22///
23/// For signatures to be valid, the s value must be less than or equal to n/2
24/// (low-s requirement). This prevents signature malleability where (r, s) and
25/// (r, n-s) are both valid signatures for the same message.
26pub const P256N_HALF: U256 =
27    uint!(0x7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8_U256);
28
29/// Normalize P256 signature s value to low-s form.
30///
31/// For any ECDSA signature (r, s), both (r, s) and (r, n-s) are valid.
32/// To prevent signature malleability, we require s <= n/2.
33/// If s > n/2, we replace it with n - s.
34///
35/// Returns an error if `s` is zero or `s >= P256_ORDER` (out of range for a
36/// valid scalar). This function should be called by all P256 signing code
37/// before creating a signature, as the p256 crate does not guarantee low-s
38/// signatures.
39pub fn normalize_p256_s(s_bytes: &[u8]) -> Result<B256, &'static str> {
40    let s = U256::from_be_slice(s_bytes);
41    if s.is_zero() || s >= P256_ORDER {
42        return Err("P256 s value out of range");
43    }
44    let normalized_s = if s > P256N_HALF { P256_ORDER - s } else { s };
45    Ok(B256::from(normalized_s.to_be_bytes::<32>()))
46}
47
48/// Signature type identifiers
49/// Note: Secp256k1 has no identifier - detected by length (65 bytes)
50pub const SIGNATURE_TYPE_P256: u8 = 0x01;
51pub const SIGNATURE_TYPE_WEBAUTHN: u8 = 0x02;
52pub const SIGNATURE_TYPE_KEYCHAIN: u8 = 0x03;
53pub const SIGNATURE_TYPE_KEYCHAIN_V2: u8 = 0x04;
54
55// Minimum authenticatorData is 37 bytes (32 rpIdHash + 1 flags + 4 signCount)
56const MIN_AUTH_DATA_LEN: usize = 37;
57
58/// WebAuthn authenticator data flags (byte 32)
59/// ref: <https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data>
60const UP: u8 = 0x01; // User Presence (bit 0)
61const UV: u8 = 0x04; // User Verified (bit 2)
62const AT: u8 = 0x40; // Attested credential data (bit 6)
63const ED: u8 = 0x80; // Extension data present (bit 7)
64
65/// P256 signature with pre-hash flag
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
69#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
70#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
71#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact))]
72pub struct P256SignatureWithPreHash {
73    pub r: B256,
74    pub s: B256,
75    pub pub_key_x: B256,
76    pub pub_key_y: B256,
77    pub pre_hash: bool,
78}
79
80/// WebAuthn signature with authenticator data
81#[derive(Clone, Debug, PartialEq, Eq, Hash)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
84#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))]
85#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
86#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact))]
87pub struct WebAuthnSignature {
88    pub r: B256,
89    pub s: B256,
90    pub pub_key_x: B256,
91    pub pub_key_y: B256,
92    /// authenticatorData || clientDataJSON (variable length)
93    pub webauthn_data: Bytes,
94}
95
96/// Primitive signature types that can be used standalone or within a Keychain signature.
97/// This enum contains only the base signature types: Secp256k1, P256, and WebAuthn.
98/// It does NOT support Keychain signatures to prevent recursion.
99///
100/// Note: This enum uses custom RLP encoding via `to_bytes()` and does NOT derive Compact.
101/// The Compact encoding is handled at the parent struct level (e.g., KeyAuthorization).
102#[derive(Clone, Debug, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "camelCase"))]
105#[cfg_attr(
106    all(test, feature = "reth-codec"),
107    reth_codecs::add_arbitrary_tests(compact, rlp)
108)]
109#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
110pub enum PrimitiveSignature {
111    /// Standard secp256k1 ECDSA signature (65 bytes: r, s, v)
112    Secp256k1(Signature),
113
114    /// P256 signature with embedded public key (129 bytes)
115    P256(P256SignatureWithPreHash),
116
117    /// WebAuthn signature with variable-length authenticator data
118    WebAuthn(WebAuthnSignature),
119}
120
121impl PrimitiveSignature {
122    /// Parse signature from bytes with backward compatibility
123    ///
124    /// For backward compatibility with existing secp256k1 signatures:
125    /// - If length is 65 bytes: treat as secp256k1 signature (no type identifier)
126    /// - Otherwise: first byte is the signature type identifier
127    pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
128        if data.is_empty() {
129            return Err("Signature data is empty");
130        }
131
132        // Backward compatibility: exactly 65 bytes means secp256k1 without type identifier
133        if data.len() == SECP256K1_SIGNATURE_LENGTH {
134            let sig = Signature::try_from(data)
135                .map_err(|_| "Failed to parse secp256k1 signature: invalid signature values")?;
136            return Ok(Self::Secp256k1(sig));
137        }
138
139        // For all other lengths, first byte is the type identifier
140        if data.len() < 2 {
141            return Err("Signature data too short: expected type identifier + signature data");
142        }
143
144        let type_id = data[0];
145        let sig_data = &data[1..];
146
147        match type_id {
148            SIGNATURE_TYPE_P256 => {
149                if sig_data.len() != P256_SIGNATURE_LENGTH {
150                    return Err("Invalid P256 signature length");
151                }
152                Ok(Self::P256(P256SignatureWithPreHash {
153                    r: B256::from_slice(&sig_data[0..32]),
154                    s: B256::from_slice(&sig_data[32..64]),
155                    pub_key_x: B256::from_slice(&sig_data[64..96]),
156                    pub_key_y: B256::from_slice(&sig_data[96..128]),
157                    pre_hash: sig_data[128] != 0,
158                }))
159            }
160            SIGNATURE_TYPE_WEBAUTHN => {
161                let len = sig_data.len();
162                if !(128..=MAX_WEBAUTHN_SIGNATURE_LENGTH).contains(&len) {
163                    return Err("Invalid WebAuthn signature length");
164                }
165                Ok(Self::WebAuthn(WebAuthnSignature {
166                    r: B256::from_slice(&sig_data[len - 128..len - 96]),
167                    s: B256::from_slice(&sig_data[len - 96..len - 64]),
168                    pub_key_x: B256::from_slice(&sig_data[len - 64..len - 32]),
169                    pub_key_y: B256::from_slice(&sig_data[len - 32..]),
170                    webauthn_data: Bytes::copy_from_slice(&sig_data[..len - 128]),
171                }))
172            }
173
174            _ => Err("Unknown signature type identifier"),
175        }
176    }
177
178    /// Encode signature to bytes
179    ///
180    /// For backward compatibility:
181    /// - Secp256k1: encoded WITHOUT type identifier (65 bytes)
182    /// - P256/WebAuthn: encoded WITH type identifier prefix
183    pub fn to_bytes(&self) -> Bytes {
184        match self {
185            Self::Secp256k1(sig) => {
186                // Backward compatibility: no type identifier for secp256k1
187                // Ensure exactly 65 bytes by using a fixed-size buffer
188                let sig_bytes = sig.as_bytes();
189                assert_eq!(
190                    sig_bytes.len(),
191                    SECP256K1_SIGNATURE_LENGTH,
192                    "Secp256k1 signature must be exactly 65 bytes"
193                );
194                Bytes::copy_from_slice(&sig_bytes)
195            }
196            Self::P256(p256_sig) => {
197                let mut bytes = Vec::with_capacity(1 + 129);
198                bytes.push(SIGNATURE_TYPE_P256);
199                bytes.extend_from_slice(p256_sig.r.as_slice());
200                bytes.extend_from_slice(p256_sig.s.as_slice());
201                bytes.extend_from_slice(p256_sig.pub_key_x.as_slice());
202                bytes.extend_from_slice(p256_sig.pub_key_y.as_slice());
203                bytes.push(if p256_sig.pre_hash { 1 } else { 0 });
204                Bytes::from(bytes)
205            }
206            Self::WebAuthn(webauthn_sig) => {
207                let mut bytes = Vec::with_capacity(1 + webauthn_sig.webauthn_data.len() + 128);
208                bytes.push(SIGNATURE_TYPE_WEBAUTHN);
209                bytes.extend_from_slice(&webauthn_sig.webauthn_data);
210                bytes.extend_from_slice(webauthn_sig.r.as_slice());
211                bytes.extend_from_slice(webauthn_sig.s.as_slice());
212                bytes.extend_from_slice(webauthn_sig.pub_key_x.as_slice());
213                bytes.extend_from_slice(webauthn_sig.pub_key_y.as_slice());
214                Bytes::from(bytes)
215            }
216        }
217    }
218
219    /// Get the length of the encoded signature in bytes
220    ///
221    /// For backward compatibility:
222    /// - Secp256k1: 65 bytes (no type identifier)
223    /// - P256/WebAuthn: includes 1-byte type identifier prefix
224    pub fn encoded_length(&self) -> usize {
225        match self {
226            Self::Secp256k1(_) => SECP256K1_SIGNATURE_LENGTH,
227            Self::P256(_) => 1 + P256_SIGNATURE_LENGTH,
228            Self::WebAuthn(webauthn_sig) => 1 + webauthn_sig.webauthn_data.len() + 128,
229        }
230    }
231
232    /// Get signature type
233    pub fn signature_type(&self) -> SignatureType {
234        match self {
235            Self::Secp256k1(_) => SignatureType::Secp256k1,
236            Self::P256(_) => SignatureType::P256,
237            Self::WebAuthn(_) => SignatureType::WebAuthn,
238        }
239    }
240
241    /// Get the in-memory size of the signature
242    pub fn size(&self) -> usize {
243        size_of::<Self>()
244            + match self {
245                Self::Secp256k1(_) | Self::P256(_) => 0,
246                Self::WebAuthn(webauthn_sig) => webauthn_sig.webauthn_data.len(),
247            }
248    }
249
250    /// Recover the signer address from the signature
251    ///
252    /// This function verifies the signature and extracts the address based on signature type:
253    /// - secp256k1: Uses standard ecrecover (signature verification + address recovery)
254    /// - P256: Verifies P256 signature then derives address from public key
255    /// - WebAuthn: Parses WebAuthn data, verifies P256 signature, derives address
256    pub fn recover_signer(
257        &self,
258        sig_hash: &B256,
259    ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
260        match self {
261            Self::Secp256k1(sig) => {
262                // Standard secp256k1 recovery using alloy's built-in methods
263                // This simultaneously verifies the signature AND recovers the address
264                alloy_consensus::crypto::secp256k1::recover_signer(sig, *sig_hash)
265            }
266            Self::P256(p256_sig) => {
267                // Prepare message hash for verification
268                let message_hash = if p256_sig.pre_hash {
269                    // Some P256 implementations (like Web Crypto) require pre-hashing
270                    B256::from_slice(Sha256::digest(sig_hash).as_ref())
271                } else {
272                    *sig_hash
273                };
274
275                // Verify P256 signature cryptographically
276                verify_p256_signature_internal(
277                    p256_sig.r.as_slice(),
278                    p256_sig.s.as_slice(),
279                    p256_sig.pub_key_x.as_slice(),
280                    p256_sig.pub_key_y.as_slice(),
281                    &message_hash,
282                )
283                .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
284
285                // Derive and return address
286                Ok(derive_p256_address(
287                    &p256_sig.pub_key_x,
288                    &p256_sig.pub_key_y,
289                ))
290            }
291            Self::WebAuthn(webauthn_sig) => {
292                // Parse and verify WebAuthn data, compute challenge hash
293                let message_hash =
294                    verify_webauthn_data_internal(&webauthn_sig.webauthn_data, sig_hash)
295                        .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
296
297                // Verify P256 signature over the computed message hash
298                verify_p256_signature_internal(
299                    webauthn_sig.r.as_slice(),
300                    webauthn_sig.s.as_slice(),
301                    webauthn_sig.pub_key_x.as_slice(),
302                    webauthn_sig.pub_key_y.as_slice(),
303                    &message_hash,
304                )
305                .map_err(|_| alloy_consensus::crypto::RecoveryError::new())?;
306
307                // Derive and return address
308                Ok(derive_p256_address(
309                    &webauthn_sig.pub_key_x,
310                    &webauthn_sig.pub_key_y,
311                ))
312            }
313        }
314    }
315}
316
317impl Default for PrimitiveSignature {
318    fn default() -> Self {
319        Self::Secp256k1(Signature::test_signature())
320    }
321}
322
323impl alloy_rlp::Encodable for PrimitiveSignature {
324    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
325        let bytes = self.to_bytes();
326        alloy_rlp::Encodable::encode(&bytes, out);
327    }
328
329    fn length(&self) -> usize {
330        alloy_rlp::Header {
331            list: false,
332            payload_length: self.encoded_length(),
333        }
334        .length_with_payload()
335    }
336}
337
338impl alloy_rlp::Decodable for PrimitiveSignature {
339    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
340        let bytes: Bytes = alloy_rlp::Decodable::decode(buf)?;
341        Self::from_bytes(&bytes).map_err(alloy_rlp::Error::Custom)
342    }
343}
344
345/// Keychain signature version.
346///
347/// Determines how the signature hash is computed for the inner signature.
348#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
349#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
350#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
351#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
352pub enum KeychainVersion {
353    /// Legacy (V1): inner signature signs `sig_hash` directly.
354    /// Deprecated at T1C.
355    /// TODO(tanishk): change default to V2 after T1C
356    #[default]
357    V1,
358    /// V2: inner signature signs `keccak256(0x04 || sig_hash || user_address)`.
359    /// Binds the signature to the specific user account with a domain separator.
360    V2,
361}
362
363/// Keychain version validation error.
364///
365/// Returned by [`TempoSignature::validate_version`] when a keychain
366/// signature's version is incompatible with the current hardfork.
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368pub enum KeychainVersionError {
369    /// Legacy V1 keychain signature used after T1C activation (permanently invalid).
370    LegacyPostT1C,
371    /// V2 keychain signature used before T1C activation (not yet valid).
372    V2BeforeActivation,
373}
374
375/// Keychain signature wrapping another signature with a user address.
376/// This allows an access key to sign on behalf of a root account.
377///
378/// No `Compact` impl — always wrapped in [`TempoSignature`] whose `Compact` delegates
379/// to `to_bytes()`/`from_bytes()` which encodes the version via the wire type byte
380/// (`0x03` = V1, `0x04` = V2).
381///
382/// Format (V1): 0x03 || user_address (20 bytes) || inner_signature
383/// Format (V2): 0x04 || user_address (20 bytes) || inner_signature
384///
385/// The user_address is the root account this transaction is being executed for.
386/// The inner signature proves an authorized access key signed the transaction.
387/// The handler validates that user_address has authorized the access key in the KeyChain precompile.
388#[derive(Clone, Debug)]
389#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
390#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
391pub struct KeychainSignature {
392    /// Root account address that this transaction is being executed for
393    pub user_address: Address,
394    /// The actual signature from the access key (can be Secp256k1, P256, or WebAuthn, but NOT another Keychain)
395    pub signature: PrimitiveSignature,
396    /// Keychain signature version (V1 = legacy, V2 = includes user_address in sig hash)
397    #[cfg_attr(feature = "serde", serde(default))]
398    pub version: KeychainVersion,
399    /// Cached access key ID recovered from the inner signature.
400    /// This is an implementation detail - use `key_id()` to access.
401    /// Uses OnceLock for thread-safe interior mutability.
402    /// Note: Excluded from PartialEq, Eq, Hash, and Compact as it's a cache.
403    #[cfg_attr(
404        feature = "serde",
405        serde(
406            serialize_with = "serialize_once_lock",
407            rename = "keyId",
408            skip_deserializing,
409        )
410    )]
411    cached_key_id: OnceLock<Address>,
412}
413
414impl KeychainSignature {
415    /// Create a new V2 KeychainSignature (recommended).
416    ///
417    /// V2 signatures include the user_address in the signature hash.
418    pub fn new(user_address: Address, signature: PrimitiveSignature) -> Self {
419        Self {
420            user_address,
421            signature,
422            version: KeychainVersion::V2,
423            cached_key_id: OnceLock::new(),
424        }
425    }
426
427    /// Create a legacy V1 KeychainSignature.
428    ///
429    /// V1 signatures do NOT include the user_address in the signature hash
430    /// and are deprecated at the T1C hardfork.
431    pub fn new_v1(user_address: Address, signature: PrimitiveSignature) -> Self {
432        Self {
433            user_address,
434            signature,
435            version: KeychainVersion::V1,
436            cached_key_id: OnceLock::new(),
437        }
438    }
439
440    /// Compute the effective signature hash for key recovery.
441    ///
442    /// - V1: returns `sig_hash` directly (legacy, deprecated)
443    /// - V2: returns `keccak256(0x04 || sig_hash || user_address)`
444    fn effective_sig_hash(&self, sig_hash: &B256) -> B256 {
445        match self.version {
446            KeychainVersion::V1 => *sig_hash,
447            KeychainVersion::V2 => Self::signing_hash(*sig_hash, self.user_address),
448        }
449    }
450
451    /// Get the access key ID for Keychain signatures.
452    ///
453    /// For Keychain signatures, this returns the access key address that signed the transaction.
454    /// The key_id is recovered from the inner signature on first access and cached for
455    /// subsequent calls. Returns None for non-Keychain signatures.
456    ///
457    /// This follows the pattern used in alloy for lazy hash computation.
458    pub fn key_id(
459        &self,
460        sig_hash: &B256,
461    ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
462        // Check if already cached
463        if let Some(cached) = self.cached_key_id.get() {
464            return Ok(*cached);
465        }
466
467        // Not cached - recover and cache
468        let effective_hash = self.effective_sig_hash(sig_hash);
469        let key_id = self.signature.recover_signer(&effective_hash)?;
470        #[allow(clippy::useless_conversion)]
471        let _ = self.cached_key_id.set(key_id.into());
472        Ok(key_id)
473    }
474
475    /// Returns true if this is a legacy V1 keychain signature.
476    pub fn is_legacy(&self) -> bool {
477        self.version == KeychainVersion::V1
478    }
479
480    /// Compute the hash that an access key should sign for a V2 keychain transaction.
481    ///
482    /// Returns `keccak256(0x04 || sig_hash || user_address)`.
483    /// The `0x04` domain separator ([`SIGNATURE_TYPE_KEYCHAIN_V2`]) prevents
484    /// cross-scheme signature confusion, following the same pattern as
485    /// EIP-7702 (`0x05`) and Tempo fee-payer signatures (`0x78`).
486    pub fn signing_hash(sig_hash: B256, user_address: Address) -> B256 {
487        let mut buf = [0u8; 53]; // 1 + 32 + 20
488        buf[0] = SIGNATURE_TYPE_KEYCHAIN_V2;
489        buf[1..33].copy_from_slice(sig_hash.as_slice());
490        buf[33..].copy_from_slice(user_address.as_slice());
491        keccak256(buf)
492    }
493}
494
495// Manual implementations of PartialEq, Eq, and Hash that exclude cached_key_id
496// since it's just a cache and doesn't affect the logical equality of signatures
497impl PartialEq for KeychainSignature {
498    fn eq(&self, other: &Self) -> bool {
499        self.user_address == other.user_address
500            && self.signature == other.signature
501            && self.version == other.version
502    }
503}
504
505impl Eq for KeychainSignature {}
506
507impl core::hash::Hash for KeychainSignature {
508    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
509        self.user_address.hash(state);
510        self.signature.hash(state);
511        self.version.hash(state);
512    }
513}
514
515// Manual Arbitrary implementation that excludes cached_key_id (cache field)
516#[cfg(any(test, feature = "arbitrary"))]
517impl<'a> arbitrary::Arbitrary<'a> for KeychainSignature {
518    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
519        Ok(Self {
520            user_address: u.arbitrary()?,
521            signature: u.arbitrary()?,
522            version: u.arbitrary()?,
523            cached_key_id: OnceLock::new(), // Always start with empty cache
524        })
525    }
526}
527
528/// AA transaction signature supporting multiple signature schemes
529///
530/// Note: Uses custom Compact implementation that delegates to `to_bytes()` / `from_bytes()`.
531#[derive(Clone, Debug, PartialEq, Eq, Hash)]
532#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
533#[cfg_attr(feature = "serde", serde(untagged, rename_all = "camelCase"))]
534#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
535#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
536pub enum TempoSignature {
537    /// Primitive signature types: Secp256k1, P256, or WebAuthn
538    Primitive(PrimitiveSignature),
539
540    /// Keychain signature - wraps another signature with a key identifier
541    /// Format: key_id (20 bytes) + inner signature
542    /// IMP: The inner signature MUST NOT be another Keychain (validated at runtime)
543    /// Note: Recursion is prevented by KeychainSignature's custom Arbitrary impl
544    Keychain(KeychainSignature),
545}
546
547impl TempoSignature {
548    /// Parse signature from bytes with backward compatibility
549    ///
550    /// For backward compatibility with existing secp256k1 signatures:
551    /// - If length is 65 bytes: treat as secp256k1 signature (no type identifier)
552    /// - Otherwise: first byte is the signature type identifier
553    pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
554        if data.is_empty() {
555            return Err("Signature data is empty");
556        }
557
558        // Check if this is a Keychain signature (type identifier 0x03 or 0x04)
559        // We need to handle this specially before delegating to PrimitiveSignature
560        if data.len() > 1
561            && data.len() != SECP256K1_SIGNATURE_LENGTH
562            && (data[0] == SIGNATURE_TYPE_KEYCHAIN || data[0] == SIGNATURE_TYPE_KEYCHAIN_V2)
563        {
564            let version = if data[0] == SIGNATURE_TYPE_KEYCHAIN {
565                KeychainVersion::V1
566            } else {
567                KeychainVersion::V2
568            };
569            let sig_data = &data[1..];
570
571            // Keychain format: user_address (20 bytes) || inner_signature
572            if sig_data.len() < 20 {
573                return Err("Invalid Keychain signature: too short for user_address");
574            }
575
576            let user_address = Address::from_slice(&sig_data[0..20]);
577            let inner_sig_bytes = &sig_data[20..];
578
579            // Parse inner signature using PrimitiveSignature (which doesn't support Keychain)
580            // This automatically prevents recursive keychain signatures at compile time
581            let inner_signature = PrimitiveSignature::from_bytes(inner_sig_bytes)?;
582
583            return Ok(Self::Keychain(KeychainSignature {
584                user_address,
585                signature: inner_signature,
586                version,
587                cached_key_id: OnceLock::new(),
588            }));
589        }
590
591        // For all non-Keychain signatures, delegate to PrimitiveSignature
592        let primitive = PrimitiveSignature::from_bytes(data)?;
593        Ok(Self::Primitive(primitive))
594    }
595
596    /// Encode signature to bytes
597    ///
598    /// For backward compatibility:
599    /// - Secp256k1: encoded WITHOUT type identifier (65 bytes)
600    /// - P256/WebAuthn: encoded WITH type identifier prefix
601    pub fn to_bytes(&self) -> Bytes {
602        match self {
603            Self::Primitive(primitive_sig) => primitive_sig.to_bytes(),
604            Self::Keychain(keychain_sig) => {
605                // Format: type_byte | user_address (20 bytes) | inner_signature
606                let inner_bytes = keychain_sig.signature.to_bytes();
607                let mut bytes = Vec::with_capacity(1 + 20 + inner_bytes.len());
608                let type_byte = match keychain_sig.version {
609                    KeychainVersion::V1 => SIGNATURE_TYPE_KEYCHAIN,
610                    KeychainVersion::V2 => SIGNATURE_TYPE_KEYCHAIN_V2,
611                };
612                bytes.push(type_byte);
613                bytes.extend_from_slice(keychain_sig.user_address.as_slice());
614                bytes.extend_from_slice(&inner_bytes);
615                Bytes::from(bytes)
616            }
617        }
618    }
619
620    /// Get the length of the encoded signature in bytes
621    ///
622    /// For backward compatibility:
623    /// - Secp256k1: 65 bytes (no type identifier)
624    /// - P256/WebAuthn: includes 1-byte type identifier prefix
625    pub fn encoded_length(&self) -> usize {
626        match self {
627            Self::Primitive(primitive_sig) => primitive_sig.encoded_length(),
628            Self::Keychain(keychain_sig) => 1 + 20 + keychain_sig.signature.encoded_length(),
629        }
630    }
631
632    /// Get signature type
633    pub fn signature_type(&self) -> SignatureType {
634        match self {
635            Self::Primitive(primitive_sig) => primitive_sig.signature_type(),
636            Self::Keychain(keychain_sig) => keychain_sig.signature.signature_type(),
637        }
638    }
639
640    /// Get the in-memory size of the signature
641    pub fn size(&self) -> usize {
642        match self {
643            Self::Primitive(primitive_sig) => primitive_sig.size(),
644            Self::Keychain(keychain_sig) => 1 + 20 + keychain_sig.signature.size(),
645        }
646    }
647
648    /// Recover the signer address from the signature
649    ///
650    /// This function verifies the signature and extracts the address based on signature type:
651    /// - secp256k1: Uses standard ecrecover (signature verification + address recovery)
652    /// - P256: Verifies P256 signature then derives address from public key
653    /// - WebAuthn: Parses WebAuthn data, verifies P256 signature, derives address
654    /// - Keychain: Validates inner signature and returns user_address
655    ///
656    /// For Keychain signatures, this performs full validation of the inner signature.
657    /// The access key address is cached in the KeychainSignature for later use.
658    /// Note: This pattern has a big footgun, that someone using recover_signer, cannot assume
659    /// that the signature is valid for the keychain. They also need to check the access key is authorized
660    /// in the keychain precompile.
661    /// We cannot check this here, as we don't have access to the keychain precompile.
662    pub fn recover_signer(
663        &self,
664        sig_hash: &B256,
665    ) -> Result<Address, alloy_consensus::crypto::RecoveryError> {
666        match self {
667            Self::Primitive(primitive_sig) => primitive_sig.recover_signer(sig_hash),
668            Self::Keychain(keychain_sig) => {
669                // Ensure validity of the keychain signature and cache the key id
670                keychain_sig.key_id(sig_hash)?;
671
672                // Return the user_address - the root account this transaction is for
673                Ok(keychain_sig.user_address)
674            }
675        }
676    }
677
678    /// Check if this is a Keychain signature
679    pub fn is_keychain(&self) -> bool {
680        matches!(self, Self::Keychain(_))
681    }
682
683    /// Check if this is a legacy V1 Keychain signature (deprecated at T1C).
684    pub fn is_legacy_keychain(&self) -> bool {
685        matches!(self, Self::Keychain(k) if k.is_legacy())
686    }
687
688    /// Check if this is a V2 Keychain signature.
689    pub fn is_v2_keychain(&self) -> bool {
690        matches!(
691            self,
692            Self::Keychain(KeychainSignature {
693                version: KeychainVersion::V2,
694                ..
695            })
696        )
697    }
698
699    /// Validates keychain signature version compatibility with the current hardfork.
700    ///
701    /// - Post-T1C: legacy V1 keychain signatures are rejected.
702    /// - Pre-T1C: V2 keychain signatures are rejected to prevent chain splits.
703    pub fn validate_version(&self, is_t1c: bool) -> Result<(), KeychainVersionError> {
704        if is_t1c && self.is_legacy_keychain() {
705            return Err(KeychainVersionError::LegacyPostT1C);
706        }
707        if !is_t1c && self.is_v2_keychain() {
708            return Err(KeychainVersionError::V2BeforeActivation);
709        }
710        Ok(())
711    }
712
713    /// Get the Keychain signature if this is a Keychain signature
714    pub fn as_keychain(&self) -> Option<&KeychainSignature> {
715        match self {
716            Self::Keychain(keychain_sig) => Some(keychain_sig),
717            _ => None,
718        }
719    }
720}
721
722impl Default for TempoSignature {
723    fn default() -> Self {
724        Self::Primitive(PrimitiveSignature::default())
725    }
726}
727
728impl alloy_rlp::Encodable for TempoSignature {
729    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
730        let bytes = self.to_bytes();
731        alloy_rlp::Encodable::encode(&bytes, out);
732    }
733
734    fn length(&self) -> usize {
735        alloy_rlp::Header {
736            list: false,
737            payload_length: self.encoded_length(),
738        }
739        .length_with_payload()
740    }
741}
742
743impl alloy_rlp::Decodable for TempoSignature {
744    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
745        let bytes: Bytes = alloy_rlp::Decodable::decode(buf)?;
746        Self::from_bytes(&bytes).map_err(alloy_rlp::Error::Custom)
747    }
748}
749
750impl From<Signature> for TempoSignature {
751    fn from(signature: Signature) -> Self {
752        Self::Primitive(PrimitiveSignature::Secp256k1(signature))
753    }
754}
755
756// ============================================================================
757// Helper Functions for Signature Verification
758// ============================================================================
759
760/// Derives a P256 address from public key coordinates
761pub fn derive_p256_address(pub_key_x: &B256, pub_key_y: &B256) -> Address {
762    let hash = keccak256([pub_key_x.as_slice(), pub_key_y.as_slice()].concat());
763
764    // Take last 20 bytes as address
765    Address::from_slice(&hash[12..])
766}
767
768/// Concatenates byte slices into a fixed-size array without heap allocations.
769fn concat<const N: usize>(slices: &[&[u8]]) -> [u8; N] {
770    let mut out = [0u8; N];
771    let mut offset = 0;
772    for s in slices {
773        out[offset..offset + s.len()].copy_from_slice(s);
774        offset += s.len();
775    }
776    debug_assert_eq!(offset, N, "slices length doesn't match array size");
777    out
778}
779
780#[cfg(feature = "std")]
781fn verify_p256_signature_with_aws_lc(
782    r: &[u8],
783    s: &[u8],
784    pub_key_x: &[u8],
785    pub_key_y: &[u8],
786    message_hash: &B256,
787) -> Result<(), &'static str> {
788    use aws_lc_rs::{
789        digest::{Digest as AwsLcDigest, SHA256 as AwsLcSha256},
790        signature::{ECDSA_P256_SHA256_FIXED, ParsedPublicKey as AwsLcParsedPublicKey},
791    };
792
793    let encoded_point = concat::<65>(&[&[0x04], pub_key_x, pub_key_y]);
794    let verifying_key = AwsLcParsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, encoded_point)
795        .map_err(|_| "Invalid P256 public key")?;
796
797    let signature = concat::<64>(&[r, s]);
798
799    // Tempo verifies already-computed 32-byte message digests.
800    // Switching to message-based aws-lc verifier would hash again and change consensus behavior.
801    let digest = AwsLcDigest::import_less_safe(message_hash.as_slice(), &AwsLcSha256)
802        .map_err(|_| "Invalid P256 message digest")?;
803
804    verifying_key
805        .verify_digest_sig(&digest, &signature)
806        .map_err(|_| "P256 signature verification failed")
807}
808
809#[cfg(any(test, not(feature = "std")))]
810fn verify_p256_signature_with_p256(
811    r: &[u8],
812    s: &[u8],
813    pub_key_x: &[u8],
814    pub_key_y: &[u8],
815    message_hash: &B256,
816) -> Result<(), &'static str> {
817    use p256::{
818        EncodedPoint,
819        ecdsa::{Signature as P256Signature, VerifyingKey, signature::hazmat::PrehashVerifier},
820    };
821
822    let encoded_point =
823        EncodedPoint::from_affine_coordinates(pub_key_x.into(), pub_key_y.into(), false);
824    let verifying_key =
825        VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| "Invalid P256 public key")?;
826
827    let signature = P256Signature::from_slice(&concat::<64>(&[r, s]))
828        .map_err(|_| "Invalid P256 signature encoding")?;
829
830    // Tempo verifies already-computed 32-byte message digests.
831    verifying_key
832        .verify_prehash(message_hash.as_slice(), &signature)
833        .map_err(|_| "P256 signature verification failed")
834}
835
836/// Verifies a P256 signature using the provided components
837///
838/// This performs actual cryptographic verification of the P256 signature
839/// according to the spec. Called during `recover_signer()` to ensure only
840/// valid signatures enter the mempool.
841///
842/// Includes a high-s value check to prevent signature malleability. For any
843/// ECDSA signature (r, s), a second valid signature (r, n-s) exists. By
844/// requiring s <= n/2 (the "low-s" requirement), we ensure only one canonical
845/// form is accepted, preventing transaction hash malleability attacks.
846///
847/// NOTE: this function conditionally compiles based on the cfg
848/// - !std → p256
849/// - std && !test → aws-lc-rs (best performance)
850/// - std && test → both (ensures verification backend alignment)
851fn verify_p256_signature_internal(
852    r: &[u8],
853    s: &[u8],
854    pub_key_x: &[u8],
855    pub_key_y: &[u8],
856    message_hash: &B256,
857) -> Result<(), &'static str> {
858    // High-s value check: reject signatures where s > n/2 to prevent malleability
859    if U256::from_be_slice(s) > P256N_HALF {
860        return Err("P256 signature has high s value");
861    }
862
863    #[cfg(all(feature = "std", not(test)))]
864    {
865        // production `std` builds use the `aws-lc-rs` crate
866        verify_p256_signature_with_aws_lc(r, s, pub_key_x, pub_key_y, message_hash)
867    }
868
869    #[cfg(not(feature = "std"))]
870    {
871        // production `no-std` builds use the `p256` crate
872        verify_p256_signature_with_p256(r, s, pub_key_x, pub_key_y, message_hash)
873    }
874
875    #[cfg(all(feature = "std", test))]
876    {
877        // test builds use both crates to verify alignment
878        let aws_lc = verify_p256_signature_with_aws_lc(r, s, pub_key_x, pub_key_y, message_hash);
879        let p256 = verify_p256_signature_with_p256(r, s, pub_key_x, pub_key_y, message_hash);
880
881        assert_eq!(
882            aws_lc.is_ok(),
883            p256.is_ok(),
884            "aws-lc and p256 verification backends disagreed"
885        );
886
887        aws_lc
888    }
889}
890
891/// Minimal struct to deserialize only the fields we need from clientDataJSON.
892/// serde_json will ignore unknown fields and only parse `type` and `challenge`.
893#[derive(serde::Deserialize)]
894struct ClientDataJson<'a> {
895    #[serde(rename = "type")]
896    type_field: &'a str,
897    challenge: &'a str,
898}
899
900/// Parses and validates WebAuthn data, returning the message hash for P256 verification.
901/// ref: <https://www.w3.org/TR/webauthn-2/#sctn-authenticator-data>
902///
903/// 1. Parses authenticatorData and clientDataJSON
904/// 2. Validates authenticatorData (min 37 bytes, UP flag set)
905/// 3. Validates clientDataJSON (type="webauthn.get", challenge matches tx_hash)
906/// 4. Computes message hash = sha256(authenticatorData || sha256(clientDataJSON))
907fn verify_webauthn_data_internal(
908    webauthn_data: &[u8],
909    tx_hash: &B256,
910) -> Result<B256, &'static str> {
911    // Ensure that we have clientDataJSON after authenticatorData
912    if webauthn_data.len() < MIN_AUTH_DATA_LEN + 32 {
913        return Err("WebAuthn data too short");
914    }
915
916    // Check flags (byte 32)
917    let flags = webauthn_data[32];
918    let (up_flag, uv_flag, at_flag, ed_flag) = (flags & UP, flags & UV, flags & AT, flags & ED);
919
920    // UP or UV flag MUST be set (UV implies user presence per WebAuthn spec)
921    if up_flag == 0 && uv_flag == 0 {
922        return Err("neither UP, nor UV flag set");
923    }
924
925    // AT flag must NOT be set for assertion signatures (`webauthn.get`)
926    if at_flag != 0 {
927        return Err("AT flag must not be set for assertion signatures");
928    }
929
930    // Determine authenticatorData length
931    let auth_data_len = if ed_flag == 0 {
932        // If ED flag is not set, exactly 37 bytes (no extensions)
933        MIN_AUTH_DATA_LEN
934    } else {
935        // ED flag must NOT be set, as Tempo AA doesn't support extensions
936        // NOTE: If we ever want to support extensions, we will have to parse CBOR data
937        return Err("ED flag must not be set, as Tempo doesn't support extensions");
938    };
939
940    let authenticator_data = &webauthn_data[..auth_data_len];
941    let client_data_json = &webauthn_data[auth_data_len..];
942
943    // Parse clientDataJSON (only extracts type and challenge fields)
944    // NOTE: Size is already bounded by MAX_WEBAUTHN_SIGNATURE_LENGTH (2KB) at signature parsing
945    let client_data: ClientDataJson<'_> =
946        serde_json::from_slice(client_data_json).map_err(|_| "clientDataJSON is not valid JSON")?;
947
948    // Validate type field
949    if client_data.type_field != "webauthn.get" {
950        return Err("clientDataJSON type must be webauthn.get");
951    }
952
953    // Validate challenge matches tx_hash (Base64URL encoded)
954    if client_data.challenge != URL_SAFE_NO_PAD.encode(tx_hash.as_slice()) {
955        return Err("clientDataJSON challenge does not match transaction hash");
956    }
957
958    // Compute message hash according to spec:
959    // messageHash = sha256(authenticatorData || sha256(clientDataJSON))
960    let client_data_hash = Sha256::digest(client_data_json);
961
962    let mut final_hasher = Sha256::new();
963    final_hasher.update(authenticator_data);
964    final_hasher.update(client_data_hash);
965    let message_hash = final_hasher.finalize();
966
967    Ok(B256::from_slice(&message_hash))
968}
969
970#[cfg(feature = "serde")]
971/// Helper function to serialize a [`OnceLock`] as an [`Option`] if it's initialized.
972fn serialize_once_lock<S>(value: &OnceLock<Address>, serializer: S) -> Result<S::Ok, S::Error>
973where
974    S: serde::Serializer,
975{
976    serde::Serialize::serialize(&value.get(), serializer)
977}
978
979#[cfg(test)]
980mod tests {
981    use super::*;
982    use alloy_primitives::hex;
983    use alloy_rlp::Encodable;
984    use base64::engine::general_purpose::URL_SAFE_NO_PAD;
985    use p256::{
986        ecdsa::{SigningKey as P256SigningKey, signature::hazmat::PrehashSigner},
987        elliptic_curve::rand_core::OsRng,
988    };
989    use proptest::prelude::*;
990    use proptest_arbitrary_interop::arb;
991
992    /// Generate P256 keypair, return (signing_key, pub_key_x, pub_key_y)
993    fn generate_p256_keypair() -> (P256SigningKey, B256, B256) {
994        let signing_key = P256SigningKey::random(&mut OsRng);
995        let verifying_key = signing_key.verifying_key();
996        let encoded_point = verifying_key.to_encoded_point(false);
997        let pub_key_x = B256::from_slice(encoded_point.x().unwrap().as_ref());
998        let pub_key_y = B256::from_slice(encoded_point.y().unwrap().as_ref());
999        (signing_key, pub_key_x, pub_key_y)
1000    }
1001
1002    /// Sign a message hash with P256, normalize s, return (r, s)
1003    fn sign_p256_normalized(signing_key: &P256SigningKey, message_hash: &B256) -> (B256, B256) {
1004        let signature: p256::ecdsa::Signature =
1005            signing_key.sign_prehash(message_hash.as_slice()).unwrap();
1006        let sig_bytes = signature.to_bytes();
1007        let r = B256::from_slice(&sig_bytes[0..32]);
1008        let s = normalize_p256_s(&sig_bytes[32..64]).expect("p256 crate produces valid s");
1009        (r, s)
1010    }
1011
1012    /// Build webauthn data with given flags and optional extension bytes
1013    fn build_webauthn_data(flags: u8, extension: Option<&[u8]>, tx_hash: &B256) -> Vec<u8> {
1014        let mut data = vec![0u8; 32]; // rpIdHash
1015        data.push(flags);
1016        data.extend_from_slice(&[0u8; 4]); // signCount
1017        if let Some(ext) = extension {
1018            data.extend_from_slice(ext);
1019        }
1020        let challenge = URL_SAFE_NO_PAD.encode(tx_hash.as_slice());
1021        data.extend_from_slice(
1022            format!("{{\"type\":\"webauthn.get\",\"challenge\":\"{challenge}\"}}").as_bytes(),
1023        );
1024        data
1025    }
1026
1027    proptest! {
1028        #[test]
1029        fn proptest_primitive_signature_rlp_length_matches_to_bytes(signature in arb::<PrimitiveSignature>()) {
1030            prop_assert_eq!(signature.to_bytes().length(), signature.length());
1031        }
1032
1033        #[test]
1034        fn proptest_tempo_signature_rlp_length_matches_to_bytes(signature in arb::<TempoSignature>()) {
1035            prop_assert_eq!(signature.to_bytes().length(), signature.length());
1036        }
1037    }
1038
1039    #[test]
1040    fn test_p256_high_s_normalization() {
1041        // s < P256N_HALF → unchanged
1042        let low_s = U256::from(1u64);
1043        let low_s_bytes: [u8; 32] = low_s.to_be_bytes();
1044        assert_eq!(
1045            U256::from_be_slice(normalize_p256_s(&low_s_bytes).unwrap().as_slice()),
1046            low_s,
1047            "s < P256N_HALF should remain unchanged"
1048        );
1049
1050        // s == P256N_HALF → unchanged
1051        let half_bytes: [u8; 32] = P256N_HALF.to_be_bytes();
1052        assert_eq!(
1053            U256::from_be_slice(normalize_p256_s(&half_bytes).unwrap().as_slice()),
1054            P256N_HALF,
1055            "s == P256N_HALF should remain unchanged"
1056        );
1057
1058        // s == P256N_HALF + 1 → normalized to P256_ORDER - s
1059        let high_s = P256N_HALF + U256::from(1u64);
1060        let high_s_bytes: [u8; 32] = high_s.to_be_bytes();
1061        assert_eq!(
1062            U256::from_be_slice(normalize_p256_s(&high_s_bytes).unwrap().as_slice()),
1063            P256_ORDER - high_s,
1064            "s > P256N_HALF should be normalized"
1065        );
1066
1067        // s == P256_ORDER - 1 → normalized to 1
1068        let max_s = P256_ORDER - U256::from(1u64);
1069        let max_s_bytes: [u8; 32] = max_s.to_be_bytes();
1070        assert_eq!(
1071            U256::from_be_slice(normalize_p256_s(&max_s_bytes).unwrap().as_slice()),
1072            U256::from(1u64),
1073            "s == P256_ORDER - 1 should normalize to 1"
1074        );
1075
1076        // s == 0 → rejected
1077        let zero_bytes: [u8; 32] = U256::ZERO.to_be_bytes();
1078        assert!(
1079            normalize_p256_s(&zero_bytes).is_err(),
1080            "s == 0 should be rejected"
1081        );
1082
1083        // s == P256_ORDER → rejected
1084        let order_bytes: [u8; 32] = P256_ORDER.to_be_bytes();
1085        assert!(
1086            normalize_p256_s(&order_bytes).is_err(),
1087            "s == P256_ORDER should be rejected"
1088        );
1089
1090        // s == P256_ORDER + 1 → rejected
1091        let over_bytes: [u8; 32] = (P256_ORDER + U256::from(1u64)).to_be_bytes();
1092        assert!(
1093            normalize_p256_s(&over_bytes).is_err(),
1094            "s > P256_ORDER should be rejected"
1095        );
1096
1097        // s == U256::MAX → rejected
1098        let max_bytes: [u8; 32] = U256::MAX.to_be_bytes();
1099        assert!(
1100            normalize_p256_s(&max_bytes).is_err(),
1101            "s == U256::MAX should be rejected"
1102        );
1103    }
1104
1105    #[test]
1106    fn test_p256_signature_verification_invalid_pubkey() {
1107        // Invalid public key should fail
1108        let r = [0u8; 32];
1109        let s = [0u8; 32];
1110        let pub_key_x = [0u8; 32]; // Invalid: point not on curve
1111        let pub_key_y = [0u8; 32];
1112        let message_hash = B256::ZERO;
1113
1114        let result = verify_p256_signature_internal(&r, &s, &pub_key_x, &pub_key_y, &message_hash);
1115        assert!(result.is_err());
1116    }
1117
1118    #[test]
1119    fn test_p256_signature_verification_invalid_signature() {
1120        let (_, pub_key_x, pub_key_y) = generate_p256_keypair();
1121        let message_hash = B256::ZERO;
1122
1123        let assert_invalid = |r: &[u8], s: &[u8], context: &str| {
1124            let result = verify_p256_signature_internal(
1125                r,
1126                s,
1127                pub_key_x.as_slice(),
1128                pub_key_y.as_slice(),
1129                &message_hash,
1130            );
1131            assert!(result.is_err(), "{context} should fail verification");
1132        };
1133
1134        // Use invalid signature (all zeros)
1135        let r = [0u8; 32];
1136        let s = [0u8; 32];
1137        assert_invalid(&r, &s, "all-zero signature");
1138
1139        let one = U256::from(1u64).to_be_bytes::<32>();
1140        let order = P256_ORDER.to_be_bytes::<32>();
1141        assert_invalid(&order, &one, "signature with r == P256_ORDER");
1142        assert_invalid(&one, &order, "signature with s == P256_ORDER");
1143    }
1144
1145    #[test]
1146    fn test_p256_signature_verification_valid() {
1147        let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1148        let message_hash = B256::from_slice(&Sha256::digest(b"test message"));
1149        let (r, s) = sign_p256_normalized(&signing_key, &message_hash);
1150
1151        let result = verify_p256_signature_internal(
1152            r.as_slice(),
1153            s.as_slice(),
1154            pub_key_x.as_slice(),
1155            pub_key_y.as_slice(),
1156            &message_hash,
1157        );
1158        assert!(
1159            result.is_ok(),
1160            "Valid P256 signature should verify successfully"
1161        );
1162    }
1163
1164    #[test]
1165    fn test_p256_high_s_rejection() {
1166        let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1167        let message_hash = B256::from_slice(&Sha256::digest(b"test message for high s"));
1168
1169        // Sign and get raw (non-normalized) signature
1170        let signature: p256::ecdsa::Signature =
1171            signing_key.sign_prehash(message_hash.as_slice()).unwrap();
1172        let sig_bytes = signature.to_bytes();
1173        let r = &sig_bytes[0..32];
1174        let original_s = &sig_bytes[32..64];
1175
1176        // Convert s to U256 and compute n - s (the high-s equivalent)
1177        let s_value = alloy_primitives::U256::from_be_slice(original_s);
1178        let computed_high_s = P256_ORDER - s_value;
1179        let computed_high_s_bytes: [u8; 32] = computed_high_s.to_be_bytes();
1180
1181        // Depending on which s was originally produced, either original or high-s
1182        // should be rejected
1183        let s_is_low = s_value <= P256N_HALF;
1184        if s_is_low {
1185            // Original s is low, so high-s version should be rejected
1186            let result = verify_p256_signature_internal(
1187                r,
1188                &computed_high_s_bytes,
1189                pub_key_x.as_slice(),
1190                pub_key_y.as_slice(),
1191                &message_hash,
1192            );
1193            assert!(
1194                result.is_err(),
1195                "High-s signature should be rejected for signature malleability prevention"
1196            );
1197            assert_eq!(result.unwrap_err(), "P256 signature has high s value");
1198        } else {
1199            // Original s was already high, so the computed "high_s" is actually low
1200            // This means the original should fail
1201            let original_result = verify_p256_signature_internal(
1202                r,
1203                original_s,
1204                pub_key_x.as_slice(),
1205                pub_key_y.as_slice(),
1206                &message_hash,
1207            );
1208            assert!(
1209                original_result.is_err(),
1210                "Original high-s signature should be rejected"
1211            );
1212        }
1213    }
1214
1215    #[test]
1216    fn test_webauthn_data_verification_too_short() {
1217        // WebAuthn data must be at least 37 bytes (authenticatorData minimum)
1218        let short_data = vec![0u8; 36];
1219        let tx_hash = B256::ZERO;
1220
1221        let result = verify_webauthn_data_internal(&short_data, &tx_hash);
1222        assert!(result.is_err());
1223        assert_eq!(result.unwrap_err(), "WebAuthn data too short");
1224    }
1225
1226    #[test]
1227    fn test_webauthn_data_verification_missing_up_and_uv_flags() {
1228        let tx_hash = B256::ZERO;
1229        let client_data = b"{\"type\":\"webauthn.get\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
1230
1231        // Create valid authenticatorData without UV nor UP flag
1232        let mut auth_data = vec![0u8; 37];
1233        auth_data[32] = 0x00;
1234        let mut webauthn_data = auth_data;
1235        webauthn_data.extend_from_slice(client_data);
1236
1237        let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1238        assert!(result.is_err());
1239        assert_eq!(result.unwrap_err(), "neither UP, nor UV flag set");
1240
1241        // Create valid authenticatorData with UV flag
1242        let mut auth_data = vec![0u8; 37];
1243        auth_data[32] = 0x04;
1244        let mut webauthn_data = auth_data;
1245        webauthn_data.extend_from_slice(client_data);
1246
1247        assert!(verify_webauthn_data_internal(&webauthn_data, &tx_hash).is_ok());
1248    }
1249
1250    #[test]
1251    fn test_webauthn_data_verification_invalid_type() {
1252        // Create valid authenticatorData with UP flag
1253        let mut auth_data = vec![0u8; 37];
1254        auth_data[32] = 0x01; // flags byte with UP flag set
1255
1256        // Add clientDataJSON with wrong type
1257        let client_data = b"{\"type\":\"webauthn.create\",\"challenge\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}";
1258        let mut webauthn_data = auth_data;
1259        webauthn_data.extend_from_slice(client_data);
1260
1261        let tx_hash = B256::ZERO;
1262        let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1263
1264        assert!(result.is_err());
1265        assert_eq!(
1266            result.unwrap_err(),
1267            "clientDataJSON type must be webauthn.get"
1268        );
1269    }
1270
1271    #[test]
1272    fn test_webauthn_data_verification_invalid_challenge() {
1273        // Create valid authenticatorData with UP flag
1274        let mut auth_data = vec![0u8; 37];
1275        auth_data[32] = 0x01; // flags byte with UP flag set
1276
1277        // Add clientDataJSON with wrong challenge
1278        let client_data =
1279            b"{\"type\":\"webauthn.get\",\"challenge\":\"wrong_challenge_value_here\"}";
1280        let mut webauthn_data = auth_data;
1281        webauthn_data.extend_from_slice(client_data);
1282
1283        let tx_hash = B256::ZERO;
1284        let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1285
1286        assert!(result.is_err());
1287        assert_eq!(
1288            result.unwrap_err(),
1289            "clientDataJSON challenge does not match transaction hash"
1290        );
1291    }
1292
1293    #[test]
1294    fn test_webauthn_data_verification_valid() {
1295        let tx_hash = B256::from_slice(&[0xAA; 32]);
1296        let webauthn_data = build_webauthn_data(0x01, None, &tx_hash); // UP flag
1297
1298        let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1299        assert!(
1300            result.is_ok(),
1301            "Valid WebAuthn data should verify successfully"
1302        );
1303
1304        // Verify the computed message hash is correct
1305        let message_hash = result.unwrap();
1306        let auth_data = &webauthn_data[..37];
1307        let client_data = &webauthn_data[37..];
1308
1309        let client_data_hash = Sha256::digest(client_data);
1310        let mut final_hasher = Sha256::new();
1311        final_hasher.update(auth_data);
1312        final_hasher.update(client_data_hash);
1313        let expected_hash = final_hasher.finalize();
1314
1315        assert_eq!(message_hash.as_slice(), &expected_hash[..]);
1316    }
1317
1318    #[test]
1319    fn test_p256_address_derivation() {
1320        let pub_key_x =
1321            hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1322        let pub_key_y =
1323            hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1324
1325        let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1326        let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1327
1328        // Should be deterministic
1329        assert_eq!(addr1, addr2);
1330
1331        // Should not be zero address
1332        assert_ne!(addr1, Address::ZERO);
1333    }
1334
1335    #[test]
1336    fn test_p256_address_derivation_deterministic() {
1337        // Test that address derivation is deterministic
1338        let pub_key_x =
1339            hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1340        let pub_key_y =
1341            hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1342
1343        let addr1 = derive_p256_address(&pub_key_x, &pub_key_y);
1344        let addr2 = derive_p256_address(&pub_key_x, &pub_key_y);
1345
1346        assert_eq!(addr1, addr2, "Address derivation should be deterministic");
1347    }
1348
1349    #[test]
1350    fn test_p256_address_different_keys_different_addresses() {
1351        // Different keys should produce different addresses
1352        let pub_key_x1 =
1353            hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1354        let pub_key_y1 =
1355            hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1356
1357        let pub_key_x2 =
1358            hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321").into();
1359        let pub_key_y2 =
1360            hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").into();
1361
1362        let addr1 = derive_p256_address(&pub_key_x1, &pub_key_y1);
1363        let addr2 = derive_p256_address(&pub_key_x2, &pub_key_y2);
1364
1365        assert_ne!(
1366            addr1, addr2,
1367            "Different keys should produce different addresses"
1368        );
1369    }
1370
1371    #[test]
1372    fn test_tempo_signature_from_bytes_secp256k1() {
1373        use super::SECP256K1_SIGNATURE_LENGTH;
1374
1375        // Secp256k1 signatures are detected by length (65 bytes), no type identifier
1376        let sig_bytes = vec![0u8; SECP256K1_SIGNATURE_LENGTH];
1377        let result = TempoSignature::from_bytes(&sig_bytes);
1378
1379        assert!(result.is_ok());
1380        if let TempoSignature::Primitive(PrimitiveSignature::Secp256k1(_)) = result.unwrap() {
1381            // Expected
1382        } else {
1383            panic!("Expected Primitive(Secp256k1) variant");
1384        }
1385    }
1386
1387    #[test]
1388    fn test_tempo_signature_from_bytes_p256() {
1389        use super::{P256_SIGNATURE_LENGTH, SIGNATURE_TYPE_P256};
1390
1391        let mut sig_bytes = vec![SIGNATURE_TYPE_P256];
1392        sig_bytes.extend_from_slice(&[0u8; P256_SIGNATURE_LENGTH]);
1393        let result = TempoSignature::from_bytes(&sig_bytes);
1394
1395        assert!(result.is_ok());
1396        if let TempoSignature::Primitive(PrimitiveSignature::P256(_)) = result.unwrap() {
1397            // Expected
1398        } else {
1399            panic!("Expected Primitive(P256) variant");
1400        }
1401    }
1402
1403    #[test]
1404    fn test_tempo_signature_from_bytes_webauthn() {
1405        use super::SIGNATURE_TYPE_WEBAUTHN;
1406
1407        let mut sig_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1408        sig_bytes.extend_from_slice(&[0u8; 200]); // 200 bytes of WebAuthn data
1409        let result = TempoSignature::from_bytes(&sig_bytes);
1410
1411        assert!(result.is_ok());
1412        if let TempoSignature::Primitive(PrimitiveSignature::WebAuthn(_)) = result.unwrap() {
1413            // Expected
1414        } else {
1415            panic!("Expected Primitive(WebAuthn) variant");
1416        }
1417    }
1418
1419    #[test]
1420    fn test_tempo_signature_from_bytes_validation() {
1421        // Empty input
1422        assert_eq!(
1423            TempoSignature::from_bytes(&[]).unwrap_err(),
1424            "Signature data is empty"
1425        );
1426        assert_eq!(
1427            PrimitiveSignature::from_bytes(&[]).unwrap_err(),
1428            "Signature data is empty"
1429        );
1430
1431        // Too short (1 byte, not secp256k1 length)
1432        assert_eq!(
1433            TempoSignature::from_bytes(&[0x01]).unwrap_err(),
1434            "Signature data too short: expected type identifier + signature data"
1435        );
1436
1437        // Wrong length for P256 (should be 129 bytes after type byte)
1438        let mut bad_p256 = vec![SIGNATURE_TYPE_P256];
1439        bad_p256.extend_from_slice(&[0u8; 100]); // wrong length
1440        assert_eq!(
1441            TempoSignature::from_bytes(&bad_p256).unwrap_err(),
1442            "Invalid P256 signature length"
1443        );
1444
1445        // Wrong length for WebAuthn (too short, < 128 bytes after type byte)
1446        let mut bad_webauthn = vec![SIGNATURE_TYPE_WEBAUTHN];
1447        bad_webauthn.extend_from_slice(&[0u8; 50]); // too short
1448        assert_eq!(
1449            TempoSignature::from_bytes(&bad_webauthn).unwrap_err(),
1450            "Invalid WebAuthn signature length"
1451        );
1452
1453        // Invalid type identifier
1454        let mut unknown_type = vec![0xFF];
1455        unknown_type.extend_from_slice(&[0u8; 100]);
1456        assert_eq!(
1457            TempoSignature::from_bytes(&unknown_type).unwrap_err(),
1458            "Unknown signature type identifier"
1459        );
1460    }
1461
1462    #[test]
1463    fn test_tempo_signature_roundtrip() {
1464        use super::{
1465            P256_SIGNATURE_LENGTH, SECP256K1_SIGNATURE_LENGTH, SIGNATURE_TYPE_P256,
1466            SIGNATURE_TYPE_WEBAUTHN,
1467        };
1468
1469        // Test secp256k1 (no type identifier, detected by 65-byte length)
1470        let sig1_bytes = vec![1u8; SECP256K1_SIGNATURE_LENGTH];
1471        let sig1 = TempoSignature::from_bytes(&sig1_bytes).unwrap();
1472        let encoded1 = sig1.to_bytes();
1473        assert_eq!(encoded1.len(), SECP256K1_SIGNATURE_LENGTH); // No type identifier
1474        // Verify roundtrip
1475        let decoded1 = TempoSignature::from_bytes(&encoded1).unwrap();
1476        assert_eq!(sig1, decoded1);
1477
1478        // Test P256
1479        let mut sig2_bytes = vec![SIGNATURE_TYPE_P256];
1480        sig2_bytes.extend_from_slice(&[2u8; P256_SIGNATURE_LENGTH]);
1481        let sig2 = TempoSignature::from_bytes(&sig2_bytes).unwrap();
1482        let encoded2 = sig2.to_bytes();
1483        assert_eq!(encoded2.len(), 1 + P256_SIGNATURE_LENGTH);
1484        // Verify roundtrip
1485        let decoded2 = TempoSignature::from_bytes(&encoded2).unwrap();
1486        assert_eq!(sig2, decoded2);
1487
1488        // Test WebAuthn
1489        let mut sig3_bytes = vec![SIGNATURE_TYPE_WEBAUTHN];
1490        sig3_bytes.extend_from_slice(&[3u8; 200]);
1491        let sig3 = TempoSignature::from_bytes(&sig3_bytes).unwrap();
1492        let encoded3 = sig3.to_bytes();
1493        assert_eq!(encoded3.len(), 1 + 200);
1494        // Verify roundtrip
1495        let decoded3 = TempoSignature::from_bytes(&encoded3).unwrap();
1496        assert_eq!(sig3, decoded3);
1497    }
1498
1499    #[test]
1500    #[cfg(feature = "serde")]
1501    fn test_tempo_signature_serde_roundtrip() {
1502        // Test serde roundtrip for all signature types
1503
1504        // Test Secp256k1
1505        let r_bytes = hex!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
1506        let s_bytes = hex!("fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321");
1507        let sig = Signature::new(
1508            alloy_primitives::U256::from_be_slice(&r_bytes),
1509            alloy_primitives::U256::from_be_slice(&s_bytes),
1510            false,
1511        );
1512        let secp256k1_sig = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(sig));
1513
1514        let json = serde_json::to_string(&secp256k1_sig).unwrap();
1515        let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1516        assert_eq!(secp256k1_sig, decoded, "Secp256k1 serde roundtrip failed");
1517
1518        // Test P256
1519        let p256_sig =
1520            TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1521                r: B256::from([1u8; 32]),
1522                s: B256::from([2u8; 32]),
1523                pub_key_x: B256::from([3u8; 32]),
1524                pub_key_y: B256::from([4u8; 32]),
1525                pre_hash: true,
1526            }));
1527
1528        let json = serde_json::to_string(&p256_sig).unwrap();
1529        let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1530        assert_eq!(p256_sig, decoded, "P256 serde roundtrip failed");
1531
1532        // Verify camelCase naming
1533        assert!(
1534            json.contains("\"pubKeyX\""),
1535            "Should use camelCase for pubKeyX"
1536        );
1537        assert!(
1538            json.contains("\"pubKeyY\""),
1539            "Should use camelCase for pubKeyY"
1540        );
1541        assert!(
1542            json.contains("\"preHash\""),
1543            "Should use camelCase for preHash"
1544        );
1545
1546        // Test WebAuthn
1547        let webauthn_sig =
1548            TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1549                r: B256::from([5u8; 32]),
1550                s: B256::from([6u8; 32]),
1551                pub_key_x: B256::from([7u8; 32]),
1552                pub_key_y: B256::from([8u8; 32]),
1553                webauthn_data: Bytes::from(vec![9u8; 50]),
1554            }));
1555
1556        let json = serde_json::to_string(&webauthn_sig).unwrap();
1557        let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
1558        assert_eq!(webauthn_sig, decoded, "WebAuthn serde roundtrip failed");
1559
1560        // Verify camelCase naming
1561        assert!(
1562            json.contains("\"pubKeyX\""),
1563            "Should use camelCase for pubKeyX"
1564        );
1565        assert!(
1566            json.contains("\"pubKeyY\""),
1567            "Should use camelCase for pubKeyY"
1568        );
1569        assert!(
1570            json.contains("\"webauthnData\""),
1571            "Should use camelCase for webauthnData"
1572        );
1573    }
1574
1575    #[test]
1576    fn test_webauthn_flag_validation() {
1577        let tx_hash = B256::ZERO;
1578
1579        // AT flag must be rejected for assertion signatures
1580        let data = build_webauthn_data(0x41, None, &tx_hash); // UP + AT
1581        let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1582        assert!(err.contains("AT flag"), "Should reject AT flag");
1583
1584        // ED flag must be rejected, as extensions are not supported
1585        let data = build_webauthn_data(0x81, Some(&[0xa0]), &tx_hash); // UP + ED, empty map
1586        let err = verify_webauthn_data_internal(&data, &tx_hash).unwrap_err();
1587        assert!(err.contains("ED flag"), "Should reject ED flag");
1588
1589        // Valid with only UP flag set
1590        let data = build_webauthn_data(0x01, None, &tx_hash); // UP only
1591        assert!(
1592            verify_webauthn_data_internal(&data, &tx_hash).is_ok(),
1593            "Should accept valid webauthn data with only UP flag"
1594        );
1595    }
1596
1597    #[test]
1598    fn test_recover_signer_p256() {
1599        let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1600        let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1601
1602        let sig_hash = B256::from([0xAA; 32]);
1603        let (r, s) = sign_p256_normalized(&signing_key, &sig_hash);
1604
1605        let p256_sig =
1606            TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1607                r,
1608                s,
1609                pub_key_x,
1610                pub_key_y,
1611                pre_hash: false,
1612            }));
1613
1614        let recovered = p256_sig.recover_signer(&sig_hash).unwrap();
1615        assert_eq!(
1616            recovered, expected_address,
1617            "P256 recovery should match derived address"
1618        );
1619    }
1620
1621    #[test]
1622    fn test_recover_signer_p256_with_prehash() {
1623        let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1624        let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1625
1626        // For pre_hash=true, signature is over sha256(sig_hash)
1627        let sig_hash = B256::from([0xBB; 32]);
1628        let prehashed = B256::from_slice(Sha256::digest(sig_hash).as_ref());
1629        let (r, s) = sign_p256_normalized(&signing_key, &prehashed);
1630
1631        let p256_sig =
1632            TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1633                r,
1634                s,
1635                pub_key_x,
1636                pub_key_y,
1637                pre_hash: true,
1638            }));
1639
1640        let recovered = p256_sig.recover_signer(&sig_hash).unwrap();
1641        assert_eq!(
1642            recovered, expected_address,
1643            "P256 pre_hash recovery should match"
1644        );
1645    }
1646
1647    #[test]
1648    fn test_recover_signer_p256_high_s_rejected() {
1649        let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1650        let sig_hash = B256::from([0xCD; 32]);
1651        let signature: p256::ecdsa::Signature =
1652            signing_key.sign_prehash(sig_hash.as_slice()).unwrap();
1653        let sig_bytes = signature.to_bytes();
1654        let r = B256::from_slice(&sig_bytes[..32]);
1655        let s_value = U256::from_be_slice(&sig_bytes[32..64]);
1656        let high_s = if s_value > P256N_HALF {
1657            s_value
1658        } else {
1659            P256_ORDER - s_value
1660        };
1661
1662        let p256_sig =
1663            TempoSignature::Primitive(PrimitiveSignature::P256(P256SignatureWithPreHash {
1664                r,
1665                s: B256::from(high_s.to_be_bytes::<32>()),
1666                pub_key_x,
1667                pub_key_y,
1668                pre_hash: false,
1669            }));
1670
1671        assert!(
1672            p256_sig.recover_signer(&sig_hash).is_err(),
1673            "high-s P256 signatures must be rejected"
1674        );
1675    }
1676
1677    #[test]
1678    fn test_recover_signer_webauthn() {
1679        let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1680        let expected_address = derive_p256_address(&pub_key_x, &pub_key_y);
1681
1682        let tx_hash = B256::from([0xCC; 32]);
1683        let webauthn_data = build_webauthn_data(0x01, None, &tx_hash);
1684
1685        let message_hash = verify_webauthn_data_internal(&webauthn_data, &tx_hash).unwrap();
1686
1687        let (r, s) = sign_p256_normalized(&signing_key, &message_hash);
1688
1689        let webauthn_sig =
1690            TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1691                r,
1692                s,
1693                pub_key_x,
1694                pub_key_y,
1695                webauthn_data: Bytes::from(webauthn_data),
1696            }));
1697
1698        let recovered = webauthn_sig.recover_signer(&tx_hash).unwrap();
1699        assert_eq!(
1700            recovered, expected_address,
1701            "WebAuthn recovery should match derived address"
1702        );
1703    }
1704
1705    #[test]
1706    fn test_recover_signer_webauthn_invalid_payload_rejected() {
1707        let (signing_key, pub_key_x, pub_key_y) = generate_p256_keypair();
1708        let tx_hash = B256::from([0xEF; 32]);
1709        let (r, s) = sign_p256_normalized(&signing_key, &B256::ZERO);
1710
1711        let invalid_webauthn_sig =
1712            TempoSignature::Primitive(PrimitiveSignature::WebAuthn(WebAuthnSignature {
1713                r,
1714                s,
1715                pub_key_x,
1716                pub_key_y,
1717                webauthn_data: Bytes::from(build_webauthn_data(0x41, None, &tx_hash)),
1718            }));
1719
1720        assert!(
1721            invalid_webauthn_sig.recover_signer(&tx_hash).is_err(),
1722            "invalid WebAuthn payloads must be rejected"
1723        );
1724    }
1725
1726    #[test]
1727    fn test_recover_signer_keychain_v1() {
1728        use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1729
1730        let (signing_key, access_key_address) = generate_secp256k1_keypair();
1731        let user_address = Address::repeat_byte(0xDD);
1732
1733        // V1: inner signature signs sig_hash directly
1734        let sig_hash = B256::from([0x22; 32]);
1735        let inner_sig = sign_hash(&signing_key, &sig_hash);
1736
1737        let keychain_sig = TempoSignature::Keychain(KeychainSignature::new_v1(
1738            user_address,
1739            match inner_sig {
1740                TempoSignature::Primitive(p) => p,
1741                _ => panic!("Expected primitive signature"),
1742            },
1743        ));
1744
1745        // recover_signer returns user_address
1746        let recovered = keychain_sig.recover_signer(&sig_hash).unwrap();
1747        assert_eq!(
1748            recovered, user_address,
1749            "Keychain V1 recovery should return user_address"
1750        );
1751
1752        // key_id should be cached and return access key address
1753        let keychain = keychain_sig.as_keychain().unwrap();
1754        let key_id = keychain.key_id(&sig_hash).unwrap();
1755        assert_eq!(
1756            key_id, access_key_address,
1757            "key_id should return access key address"
1758        );
1759
1760        // V1 should be legacy
1761        assert!(keychain_sig.is_legacy_keychain());
1762    }
1763
1764    #[test]
1765    fn test_recover_signer_keychain_v2() {
1766        use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1767
1768        let (signing_key, access_key_address) = generate_secp256k1_keypair();
1769        let user_address = Address::repeat_byte(0xDD);
1770
1771        // V2: inner signature signs keccak256(0x04 || sig_hash || user_address)
1772        let sig_hash = B256::from([0x22; 32]);
1773        let mut buf = [0u8; 53]; // 1 + 32 + 20
1774        buf[0] = SIGNATURE_TYPE_KEYCHAIN_V2;
1775        buf[1..33].copy_from_slice(sig_hash.as_slice());
1776        buf[33..].copy_from_slice(user_address.as_slice());
1777        let effective_hash = keccak256(buf);
1778        let inner_sig = sign_hash(&signing_key, &effective_hash);
1779
1780        let keychain_sig = TempoSignature::Keychain(KeychainSignature::new(
1781            user_address,
1782            match inner_sig {
1783                TempoSignature::Primitive(p) => p,
1784                _ => panic!("Expected primitive signature"),
1785            },
1786        ));
1787
1788        // recover_signer returns user_address
1789        let recovered = keychain_sig.recover_signer(&sig_hash).unwrap();
1790        assert_eq!(
1791            recovered, user_address,
1792            "Keychain V2 recovery should return user_address"
1793        );
1794
1795        // key_id should be cached and return access key address
1796        let keychain = keychain_sig.as_keychain().unwrap();
1797        let key_id = keychain.key_id(&sig_hash).unwrap();
1798        assert_eq!(
1799            key_id, access_key_address,
1800            "key_id should return access key address"
1801        );
1802
1803        // V2 should NOT be legacy
1804        assert!(!keychain_sig.is_legacy_keychain());
1805    }
1806
1807    #[test]
1808    fn test_keychain_v2_binds_user_address() {
1809        use crate::transaction::tt_authorization::tests::{generate_secp256k1_keypair, sign_hash};
1810
1811        let (signing_key, _access_key_address) = generate_secp256k1_keypair();
1812        let user_a = Address::repeat_byte(0xAA);
1813        let user_b = Address::repeat_byte(0xBB);
1814
1815        // Sign for user_a with V2
1816        let sig_hash = B256::from([0x22; 32]);
1817        let effective_hash = KeychainSignature::signing_hash(sig_hash, user_a);
1818        let inner_sig = sign_hash(&signing_key, &effective_hash);
1819
1820        let inner_primitive = match inner_sig {
1821            TempoSignature::Primitive(p) => p,
1822            _ => panic!("Expected primitive signature"),
1823        };
1824
1825        // Valid for user_a
1826        let sig_a =
1827            TempoSignature::Keychain(KeychainSignature::new(user_a, inner_primitive.clone()));
1828        let recovered_a = sig_a.recover_signer(&sig_hash).unwrap();
1829        assert_eq!(recovered_a, user_a);
1830
1831        // Same inner signature but for user_b — key_id will differ
1832        // because user_address is part of the signed hash
1833        let sig_b = TempoSignature::Keychain(KeychainSignature::new(user_b, inner_primitive));
1834        let recovered_b = sig_b.recover_signer(&sig_hash).unwrap();
1835        assert_eq!(
1836            recovered_b, user_b,
1837            "recover_signer returns the claimed user_address"
1838        );
1839
1840        // But the key_id recovered under user_b will be a garbage address (not the real access key)
1841        let key_id_a = sig_a.as_keychain().unwrap().key_id(&sig_hash).unwrap();
1842        let key_id_b = sig_b.as_keychain().unwrap().key_id(&sig_hash).unwrap();
1843        assert_ne!(
1844            key_id_a, key_id_b,
1845            "V2 should recover different key_ids for different user_addresses"
1846        );
1847    }
1848
1849    #[test]
1850    fn test_signing_hash_properties() {
1851        let hash_a = B256::from([0x11; 32]);
1852        let hash_b = B256::from([0x22; 32]);
1853        let addr_a = Address::repeat_byte(0xAA);
1854        let addr_b = Address::repeat_byte(0xBB);
1855
1856        // Different addresses produce different signing hashes
1857        assert_ne!(
1858            KeychainSignature::signing_hash(hash_a, addr_a),
1859            KeychainSignature::signing_hash(hash_a, addr_b),
1860        );
1861
1862        // Different tx hashes produce different signing hashes
1863        assert_ne!(
1864            KeychainSignature::signing_hash(hash_a, addr_a),
1865            KeychainSignature::signing_hash(hash_b, addr_a),
1866        );
1867
1868        // Deterministic: same inputs yield same output
1869        assert_eq!(
1870            KeychainSignature::signing_hash(hash_a, addr_a),
1871            KeychainSignature::signing_hash(hash_a, addr_a),
1872        );
1873    }
1874
1875    #[test]
1876    fn test_webauthn_rejects_challenge_injection() {
1877        let (tx_hash, attack_hash) = (B256::from([0xAA; 32]), B256::from([0xFF; 32]));
1878        let (challenge, attack_challenge) = (
1879            URL_SAFE_NO_PAD.encode(tx_hash.as_slice()),
1880            URL_SAFE_NO_PAD.encode(attack_hash.as_slice()),
1881        );
1882
1883        // Ensure that the happy path works
1884        let valid_payload = format!(r#"{{"type":"webauthn.get","challenge":"{challenge}"}}"#);
1885
1886        let mut auth_data = vec![0u8; 37];
1887        auth_data[32] = 0x01;
1888        let mut webauthn_data = auth_data;
1889        webauthn_data.extend_from_slice(valid_payload.as_bytes());
1890
1891        let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1892        assert!(result.is_ok());
1893
1894        // Ensure that malicious payloads cannot pass validation
1895        let attack_variants = [
1896            format!(
1897                r#"{{"type":"webauthn.get","challenge":"{attack_challenge}","extra":{{"challenge":"{challenge}"}}}}"#
1898            ),
1899            format!(
1900                r#"{{"type":"webauthn.get","data":[{{"challenge":"{challenge}"}}],"challenge":"{attack_challenge}"}}"#
1901            ),
1902        ];
1903
1904        for (i, attack_json) in attack_variants.iter().enumerate() {
1905            let mut auth_data = vec![0u8; 37];
1906            auth_data[32] = 0x01;
1907            let mut webauthn_data = auth_data;
1908            webauthn_data.extend_from_slice(attack_json.as_bytes());
1909
1910            let result = verify_webauthn_data_internal(&webauthn_data, &tx_hash);
1911            assert!(
1912                result.is_err(),
1913                "Attack variant {i} should be rejected: {attack_json}"
1914            );
1915        }
1916    }
1917
1918    #[test]
1919    fn test_keychain_signature_eq_same() {
1920        let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1921        let addr = Address::repeat_byte(0x01);
1922        let a = KeychainSignature::new(addr, sig.clone());
1923        let b = KeychainSignature::new(addr, sig);
1924        assert_eq!(a, b);
1925    }
1926
1927    #[test]
1928    fn test_keychain_signature_eq_different_address() {
1929        let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1930        let a = KeychainSignature::new(Address::repeat_byte(0x01), sig.clone());
1931        let b = KeychainSignature::new(Address::repeat_byte(0x02), sig);
1932        assert_ne!(a, b);
1933    }
1934
1935    #[test]
1936    fn test_keychain_signature_eq_different_signature() {
1937        let addr = Address::repeat_byte(0x01);
1938        let sig_a = PrimitiveSignature::Secp256k1(Signature::test_signature());
1939        let sig_b = PrimitiveSignature::P256(P256SignatureWithPreHash {
1940            r: B256::from([1u8; 32]),
1941            s: B256::from([2u8; 32]),
1942            pub_key_x: B256::from([3u8; 32]),
1943            pub_key_y: B256::from([4u8; 32]),
1944            pre_hash: false,
1945        });
1946        let a = KeychainSignature::new(addr, sig_a);
1947        let b = KeychainSignature::new(addr, sig_b);
1948        assert_ne!(a, b);
1949    }
1950
1951    #[test]
1952    fn test_keychain_signature_hash_differs_for_different_sigs() {
1953        use std::{
1954            collections::hash_map::DefaultHasher,
1955            hash::{Hash, Hasher},
1956        };
1957
1958        let sig = PrimitiveSignature::Secp256k1(Signature::test_signature());
1959        let a = KeychainSignature::new(Address::repeat_byte(0x01), sig.clone());
1960        let b = KeychainSignature::new(Address::repeat_byte(0x02), sig.clone());
1961        let c = KeychainSignature::new(Address::repeat_byte(0x01), sig);
1962
1963        let hash = |k: &KeychainSignature| {
1964            let mut h = DefaultHasher::new();
1965            k.hash(&mut h);
1966            h.finish()
1967        };
1968
1969        assert_ne!(
1970            hash(&a),
1971            hash(&b),
1972            "different address should produce different hash"
1973        );
1974        assert_eq!(hash(&a), hash(&c), "same fields should produce same hash");
1975    }
1976
1977    #[test]
1978    fn test_primitive_signature_from_bytes_one_byte() {
1979        let result = PrimitiveSignature::from_bytes(&[0x01]);
1980        assert!(result.is_err());
1981        assert!(result.unwrap_err().contains("too short"));
1982    }
1983
1984    #[test]
1985    fn test_tempo_signature_keychain_too_short_for_address() {
1986        for type_byte in [SIGNATURE_TYPE_KEYCHAIN, SIGNATURE_TYPE_KEYCHAIN_V2] {
1987            let mut data = vec![type_byte];
1988            data.extend_from_slice(&[0u8; 19]);
1989            let result = TempoSignature::from_bytes(&data);
1990            assert!(result.is_err());
1991            assert!(result.unwrap_err().contains("too short"));
1992        }
1993    }
1994
1995    #[test]
1996    fn test_tempo_signature_keychain_exactly_20_bytes_inner_empty() {
1997        let mut data = vec![SIGNATURE_TYPE_KEYCHAIN];
1998        data.extend_from_slice(&[0u8; 20]);
1999        let result = TempoSignature::from_bytes(&data);
2000        assert!(result.is_err());
2001    }
2002
2003    #[test]
2004    fn test_is_keychain_returns_false_for_primitive() {
2005        let sig =
2006            TempoSignature::Primitive(PrimitiveSignature::Secp256k1(Signature::test_signature()));
2007        assert!(!sig.is_keychain());
2008    }
2009
2010    #[test]
2011    fn test_is_keychain_returns_true_for_keychain() {
2012        let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2013        let sig = TempoSignature::Keychain(KeychainSignature::new(Address::ZERO, inner));
2014        assert!(sig.is_keychain());
2015    }
2016
2017    #[test]
2018    fn test_keychain_v1_v2_bytes_roundtrip_and_wire_format() {
2019        let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2020        let user = Address::repeat_byte(0xAA);
2021
2022        // V1 round-trips and uses 0x03 wire byte
2023        let v1 = TempoSignature::Keychain(KeychainSignature::new_v1(user, inner.clone()));
2024        let v1_bytes = v1.to_bytes();
2025        assert_eq!(v1_bytes[0], SIGNATURE_TYPE_KEYCHAIN);
2026        let v1_decoded = TempoSignature::from_bytes(&v1_bytes).unwrap();
2027        assert_eq!(v1, v1_decoded);
2028        assert!(v1_decoded.is_legacy_keychain());
2029
2030        // V2 round-trips and uses 0x04 wire byte
2031        let v2 = TempoSignature::Keychain(KeychainSignature::new(user, inner));
2032        let v2_bytes = v2.to_bytes();
2033        assert_eq!(v2_bytes[0], SIGNATURE_TYPE_KEYCHAIN_V2);
2034        let v2_decoded = TempoSignature::from_bytes(&v2_bytes).unwrap();
2035        assert_eq!(v2, v2_decoded);
2036        assert!(!v2_decoded.is_legacy_keychain());
2037
2038        // V1 and V2 with same inner sig are NOT equal
2039        assert_ne!(v1, v2);
2040    }
2041
2042    #[test]
2043    #[cfg(feature = "serde")]
2044    fn test_keychain_serde_roundtrip_and_backward_compat() {
2045        let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2046        let user = Address::repeat_byte(0xBB);
2047
2048        // V2 serde roundtrip preserves version
2049        let v2 = TempoSignature::Keychain(KeychainSignature::new(user, inner.clone()));
2050        let json = serde_json::to_string(&v2).unwrap();
2051        let decoded: TempoSignature = serde_json::from_str(&json).unwrap();
2052        assert_eq!(v2, decoded);
2053        assert!(!decoded.is_legacy_keychain());
2054
2055        // V1 serde roundtrip preserves version
2056        let v1 = TempoSignature::Keychain(KeychainSignature::new_v1(user, inner));
2057        let json_v1 = serde_json::to_string(&v1).unwrap();
2058        let decoded_v1: TempoSignature = serde_json::from_str(&json_v1).unwrap();
2059        assert_eq!(v1, decoded_v1);
2060        assert!(decoded_v1.is_legacy_keychain());
2061
2062        // Backward compat: JSON without "version" field deserializes as V1
2063        let json_no_version = json_v1.replace(r#","version":"v1""#, "");
2064        assert!(
2065            !json_no_version.contains("version"),
2066            "version field should be stripped"
2067        );
2068        let decoded_no_version: TempoSignature = serde_json::from_str(&json_no_version).unwrap();
2069        assert!(decoded_no_version.is_legacy_keychain());
2070    }
2071
2072    #[test]
2073    fn test_keychain_rlp_roundtrip_preserves_version() {
2074        use alloy_rlp::Decodable;
2075
2076        let inner = PrimitiveSignature::Secp256k1(Signature::test_signature());
2077        let user = Address::repeat_byte(0xCC);
2078
2079        for (sig, expect_legacy) in [
2080            (
2081                TempoSignature::Keychain(KeychainSignature::new_v1(user, inner.clone())),
2082                true,
2083            ),
2084            (
2085                TempoSignature::Keychain(KeychainSignature::new(user, inner)),
2086                false,
2087            ),
2088        ] {
2089            let mut buf = Vec::new();
2090            alloy_rlp::Encodable::encode(&sig, &mut buf);
2091            let decoded = TempoSignature::decode(&mut buf.as_slice()).unwrap();
2092            assert_eq!(sig, decoded);
2093            assert_eq!(decoded.is_legacy_keychain(), expect_legacy);
2094        }
2095    }
2096}
2097
2098#[cfg(all(test, feature = "reth-codec"))]
2099mod compact_tests {
2100    use super::*;
2101    use alloy_primitives::{b256, bytes, hex};
2102    use reth_codecs::Compact;
2103
2104    /// Ensures backwards compatibility of compact bitflags.
2105    ///
2106    /// See reth's `HeaderExt` pattern:
2107    /// <https://github.com/paradigmxyz/reth-core/blob/0476d1bc4b71f3c3b080622be297edd91ee4e70c/crates/codecs/src/alloy/header.rs>
2108    #[test]
2109    fn compact_types_have_unused_bits() {
2110        assert_ne!(
2111            P256SignatureWithPreHash::bitflag_unused_bits(),
2112            0,
2113            "P256SignatureWithPreHash"
2114        );
2115    }
2116
2117    #[test]
2118    fn p256_signature_compact_roundtrip() {
2119        let sig = P256SignatureWithPreHash {
2120            r: b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
2121            s: b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
2122            pub_key_x: b256!("0x3333333333333333333333333333333333333333333333333333333333333333"),
2123            pub_key_y: b256!("0x4444444444444444444444444444444444444444444444444444444444444444"),
2124            pre_hash: true,
2125        };
2126
2127        let expected = hex!(
2128            "011111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444"
2129        );
2130
2131        let mut buf = vec![];
2132        let len = sig.to_compact(&mut buf);
2133        assert_eq!(
2134            buf, expected,
2135            "P256SignatureWithPreHash compact encoding changed"
2136        );
2137        assert_eq!(len, expected.len());
2138
2139        let (decoded, _) = P256SignatureWithPreHash::from_compact(&expected, expected.len());
2140        assert_eq!(decoded, sig);
2141    }
2142
2143    #[test]
2144    fn webauthn_signature_compact_roundtrip() {
2145        let sig = WebAuthnSignature {
2146            r: b256!("0x1111111111111111111111111111111111111111111111111111111111111111"),
2147            s: b256!("0x2222222222222222222222222222222222222222222222222222222222222222"),
2148            pub_key_x: b256!("0x3333333333333333333333333333333333333333333333333333333333333333"),
2149            pub_key_y: b256!("0x4444444444444444444444444444444444444444444444444444444444444444"),
2150            webauthn_data: bytes!("aabbccdd"),
2151        };
2152
2153        let expected = hex!(
2154            "1111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444aabbccdd"
2155        );
2156
2157        let mut buf = vec![];
2158        let len = sig.to_compact(&mut buf);
2159        assert_eq!(buf, expected, "WebAuthnSignature compact encoding changed");
2160        assert_eq!(len, expected.len());
2161
2162        let (decoded, _) = WebAuthnSignature::from_compact(&expected, expected.len());
2163        assert_eq!(decoded, sig);
2164    }
2165}