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