Skip to main content

foundry_cheatcodes/
crypto.rs

1//! Implementations of [`Crypto`](spec::Group::Crypto) Cheatcodes.
2
3use crate::{Cheatcode, Cheatcodes, Result, Vm::*};
4use alloy_primitives::{Address, B256, U256, keccak256};
5use alloy_signer::{Signer, SignerSync};
6use alloy_signer_local::{
7    LocalSigner, MnemonicBuilder, PrivateKeySigner,
8    coins_bip39::{
9        ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, Korean,
10        Portuguese, Spanish, Wordlist,
11    },
12};
13use alloy_sol_types::SolValue;
14use foundry_common::wallet::{derive_private_key, derive_private_key_with_language};
15use foundry_evm_core::evm::FoundryEvmNetwork;
16use k256::{
17    AffinePoint, EncodedPoint, FieldBytes, FieldElement, ProjectivePoint, Scalar,
18    ecdsa::{SigningKey, hazmat},
19    elliptic_curve::{
20        bigint::{ArrayEncoding, U256 as K256U256},
21        group::Group,
22        ops::Reduce,
23        sec1::{FromEncodedPoint, ToEncodedPoint},
24    },
25};
26
27use p256::ecdsa::{
28    Signature as P256Signature, SigningKey as P256SigningKey, signature::hazmat::PrehashSigner,
29};
30
31use ed25519_consensus::{
32    Signature as Ed25519Signature, SigningKey as Ed25519SigningKey,
33    VerificationKey as Ed25519VerificationKey,
34};
35use tempo_primitives::transaction::{KeychainSignature, PrimitiveSignature, TempoSignature};
36
37/// The BIP32 default derivation path prefix.
38const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/";
39const PRIVATE_KEY_SIGNER_CACHE_LIMIT: usize = 64;
40
41impl Cheatcode for createWallet_0Call {
42    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
43        let Self { walletLabel } = self;
44        create_wallet(&U256::from_be_bytes(keccak256(walletLabel).0), Some(walletLabel), state)
45    }
46}
47
48impl Cheatcode for createWallet_1Call {
49    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
50        let Self { privateKey } = self;
51        create_wallet(privateKey, None, state)
52    }
53}
54
55impl Cheatcode for createWallet_2Call {
56    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
57        let Self { privateKey, walletLabel } = self;
58        create_wallet(privateKey, Some(walletLabel), state)
59    }
60}
61
62impl Cheatcode for sign_0Call {
63    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
64        let Self { wallet, digest } = self;
65        let sig = sign(&wallet.privateKey, digest)?;
66        Ok(encode_full_sig(sig))
67    }
68}
69
70impl Cheatcode for signWithNonceUnsafeCall {
71    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
72        let pk: U256 = self.privateKey;
73        let digest: B256 = self.digest;
74        let nonce: U256 = self.nonce;
75        let sig: alloy_primitives::Signature = sign_with_nonce(&pk, &digest, &nonce)?;
76        Ok(encode_full_sig(sig))
77    }
78}
79
80impl Cheatcode for signKeychainCall {
81    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
82        let Self { privateKey, account, digest } = self;
83        sign_keychain(state, privateKey, account, digest)
84    }
85}
86
87impl Cheatcode for signKeychainAdminCall {
88    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
89        let Self { privateKey, account, digest } = self;
90        sign_keychain(state, privateKey, account, digest)
91    }
92}
93
94impl Cheatcode for signCompact_0Call {
95    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
96        let Self { wallet, digest } = self;
97        let sig = sign(&wallet.privateKey, digest)?;
98        Ok(encode_compact_sig(sig))
99    }
100}
101
102impl Cheatcode for deriveKey_0Call {
103    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
104        let Self { mnemonic, index } = self;
105        derive_key::<English>(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index)
106    }
107}
108
109impl Cheatcode for deriveKey_1Call {
110    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
111        let Self { mnemonic, derivationPath, index } = self;
112        derive_key::<English>(mnemonic, derivationPath, *index)
113    }
114}
115
116impl Cheatcode for deriveKey_2Call {
117    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
118        let Self { mnemonic, index, language } = self;
119        derive_key_str(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index, language)
120    }
121}
122
123impl Cheatcode for deriveKey_3Call {
124    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
125        let Self { mnemonic, derivationPath, index, language } = self;
126        derive_key_str(mnemonic, derivationPath, *index, language)
127    }
128}
129
130impl Cheatcode for rememberKeyCall {
131    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
132        let Self { privateKey } = self;
133        let wallet = with_private_key_signer(state, privateKey, |wallet| Ok(wallet.clone()))?;
134        let address = inject_wallet(state, wallet);
135        Ok(address.abi_encode())
136    }
137}
138
139impl Cheatcode for rememberKeys_0Call {
140    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
141        let Self { mnemonic, derivationPath, count } = self;
142        let wallets = derive_wallets::<English>(mnemonic, derivationPath, *count)?;
143        let mut addresses = Vec::<Address>::with_capacity(wallets.len());
144        for wallet in wallets {
145            let addr = inject_wallet(state, wallet);
146            addresses.push(addr);
147        }
148
149        Ok(addresses.abi_encode())
150    }
151}
152
153impl Cheatcode for rememberKeys_1Call {
154    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
155        let Self { mnemonic, derivationPath, language, count } = self;
156        let wallets = derive_wallets_str(mnemonic, derivationPath, language, *count)?;
157        let mut addresses = Vec::<Address>::with_capacity(wallets.len());
158        for wallet in wallets {
159            let addr = inject_wallet(state, wallet);
160            addresses.push(addr);
161        }
162
163        Ok(addresses.abi_encode())
164    }
165}
166
167fn inject_wallet<FEN: FoundryEvmNetwork>(
168    state: &mut Cheatcodes<FEN>,
169    wallet: LocalSigner<SigningKey>,
170) -> Address {
171    let address = wallet.address();
172    state.wallets().add_local_signer(wallet);
173    address
174}
175
176impl Cheatcode for sign_1Call {
177    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
178        let Self { privateKey, digest } = self;
179        let sig = sign_cached(state, privateKey, digest)?;
180        Ok(encode_full_sig(sig))
181    }
182}
183
184impl Cheatcode for signCompact_1Call {
185    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
186        let Self { privateKey, digest } = self;
187        let sig = sign_cached(state, privateKey, digest)?;
188        Ok(encode_compact_sig(sig))
189    }
190}
191
192impl Cheatcode for sign_2Call {
193    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
194        let Self { digest } = self;
195        let sig = sign_with_wallet(state, None, digest)?;
196        Ok(encode_full_sig(sig))
197    }
198}
199
200impl Cheatcode for signCompact_2Call {
201    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
202        let Self { digest } = self;
203        let sig = sign_with_wallet(state, None, digest)?;
204        Ok(encode_compact_sig(sig))
205    }
206}
207
208impl Cheatcode for sign_3Call {
209    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
210        let Self { signer, digest } = self;
211        let sig = sign_with_wallet(state, Some(*signer), digest)?;
212        Ok(encode_full_sig(sig))
213    }
214}
215
216impl Cheatcode for signCompact_3Call {
217    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
218        let Self { signer, digest } = self;
219        let sig = sign_with_wallet(state, Some(*signer), digest)?;
220        Ok(encode_compact_sig(sig))
221    }
222}
223
224impl Cheatcode for signP256Call {
225    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
226        let Self { privateKey, digest } = self;
227        sign_p256(privateKey, digest)
228    }
229}
230
231impl Cheatcode for publicKeyP256Call {
232    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
233        let Self { privateKey } = self;
234        let pub_key =
235            parse_private_key_p256(privateKey)?.verifying_key().as_affine().to_encoded_point(false);
236        let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into());
237        let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into());
238
239        Ok((pub_key_x, pub_key_y).abi_encode())
240    }
241}
242
243impl Cheatcode for ecAffineToProjectiveCall {
244    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
245        let Self { pointX, pointY } = self;
246        let point = parse_affine_point(pointX, pointY, "point")?;
247        encode_projective_point(ProjectivePoint::from(point))
248    }
249}
250
251impl Cheatcode for ecProjectiveToAffineCall {
252    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
253        let Self { pointX, pointY, pointZ } = self;
254        let point = parse_projective_point(pointX, pointY, pointZ, "point")?;
255        encode_affine_point(point)
256    }
257}
258
259impl Cheatcode for ecAddAffineCall {
260    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
261        let Self { pointX1, pointY1, pointX2, pointY2 } = self;
262        let lhs = parse_affine_point(pointX1, pointY1, "first point")?;
263        let rhs = parse_affine_point(pointX2, pointY2, "second point")?;
264        encode_affine_point(ProjectivePoint::from(lhs) + rhs)
265    }
266}
267
268impl Cheatcode for ecAddProjectiveCall {
269    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
270        let Self { pointX1, pointY1, pointZ1, pointX2, pointY2, pointZ2 } = self;
271        let lhs = parse_projective_point(pointX1, pointY1, pointZ1, "first point")?;
272        let rhs = parse_projective_point(pointX2, pointY2, pointZ2, "second point")?;
273        encode_projective_point(lhs + rhs)
274    }
275}
276
277impl Cheatcode for ecMulAffineCall {
278    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
279        let Self { pointX, pointY, scalar } = self;
280        let point = parse_affine_point(pointX, pointY, "point")?;
281        let scalar = reduce_ec_scalar(scalar);
282        encode_affine_point(ProjectivePoint::from(point) * scalar)
283    }
284}
285
286impl Cheatcode for ecMulProjectiveCall {
287    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
288        let Self { pointX, pointY, pointZ, scalar } = self;
289        let point = parse_projective_point(pointX, pointY, pointZ, "point")?;
290        let scalar = reduce_ec_scalar(scalar);
291        encode_projective_point(point * scalar)
292    }
293}
294
295impl Cheatcode for createEd25519KeyCall {
296    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
297        let Self { salt } = self;
298        create_ed25519_key(salt)
299    }
300}
301
302impl Cheatcode for publicKeyEd25519Call {
303    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
304        let Self { privateKey } = self;
305        public_key_ed25519(privateKey)
306    }
307}
308
309impl Cheatcode for signEd25519Call {
310    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
311        let Self { namespace, message, privateKey } = self;
312        sign_ed25519(namespace, message, privateKey)
313    }
314}
315
316impl Cheatcode for verifyEd25519Call {
317    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
318        let Self { signature, namespace, message, publicKey } = self;
319        verify_ed25519(signature, namespace, message, publicKey)
320    }
321}
322
323/// Using a given private key, return its public ETH address, its public key affine x and y
324/// coordinates, and its private key (see the 'Wallet' struct)
325///
326/// If 'label' is set to 'Some()', assign that label to the associated ETH address in state
327fn create_wallet<FEN: FoundryEvmNetwork>(
328    private_key: &U256,
329    label: Option<&str>,
330    state: &mut Cheatcodes<FEN>,
331) -> Result {
332    let (addr, pub_key_x, pub_key_y) = with_private_key_signer(state, private_key, |wallet| {
333        let addr = wallet.address();
334        let pub_key = wallet.credential().verifying_key().as_affine().to_encoded_point(false);
335        let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into());
336        let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into());
337        Ok((addr, pub_key_x, pub_key_y))
338    })?;
339
340    if let Some(label) = label {
341        state.labels.insert(addr, label.into());
342    }
343
344    Ok(Wallet { addr, publicKeyX: pub_key_x, publicKeyY: pub_key_y, privateKey: *private_key }
345        .abi_encode())
346}
347
348fn encode_full_sig(sig: alloy_primitives::Signature) -> Vec<u8> {
349    // Retrieve v, r and s from signature.
350    let v = U256::from(sig.v_byte());
351    let r = B256::from(sig.r());
352    let s = B256::from(sig.s());
353    (v, r, s).abi_encode()
354}
355
356fn encode_compact_sig(sig: alloy_primitives::Signature) -> Vec<u8> {
357    // Implement EIP-2098 compact signature.
358    let r = B256::from(sig.r());
359    let mut vs = sig.s();
360    vs.set_bit(255, sig.v());
361    (r, vs).abi_encode()
362}
363
364fn sign(private_key: &U256, digest: &B256) -> Result<alloy_primitives::Signature> {
365    // The `ecrecover` precompile does not use EIP-155. No chain ID is needed.
366    let wallet = parse_wallet(private_key)?;
367    let sig = wallet.sign_hash_sync(digest)?;
368    debug_assert_eq!(sig.recover_address_from_prehash(digest)?, wallet.address());
369    Ok(sig)
370}
371
372fn sign_cached<FEN: FoundryEvmNetwork>(
373    state: &mut Cheatcodes<FEN>,
374    private_key: &U256,
375    digest: &B256,
376) -> Result<alloy_primitives::Signature> {
377    with_private_key_signer(state, private_key, |wallet| {
378        let sig = wallet.sign_hash_sync(digest)?;
379        debug_assert_eq!(sig.recover_address_from_prehash(digest)?, wallet.address());
380        Ok(sig)
381    })
382}
383
384fn sign_keychain<FEN: FoundryEvmNetwork>(
385    state: &mut Cheatcodes<FEN>,
386    private_key: &U256,
387    account: &Address,
388    digest: &B256,
389) -> Result {
390    let signing_hash = KeychainSignature::signing_hash(*digest, *account);
391    let inner = sign_cached(state, private_key, &signing_hash)?;
392    let signature = TempoSignature::Keychain(KeychainSignature::new(
393        *account,
394        PrimitiveSignature::Secp256k1(inner),
395    ));
396    Ok(signature.to_bytes().abi_encode())
397}
398
399/// Signs `digest` on secp256k1 using a user-supplied ephemeral nonce `k` (no RFC6979).
400/// - `private_key` and `nonce` must be in (0, n)
401/// - `digest` is a 32-byte prehash.
402///
403/// # Warning
404///
405/// Use [`sign_with_nonce`] with extreme caution!
406/// Reusing the same nonce (`k`) with the same private key in ECDSA will leak the private key.
407/// Always generate `nonce` with a cryptographically secure RNG, and never reuse it across
408/// signatures.
409fn sign_with_nonce(
410    private_key: &U256,
411    digest: &B256,
412    nonce: &U256,
413) -> Result<alloy_primitives::Signature> {
414    let d_scalar: Scalar =
415        <Scalar as k256::elliptic_curve::PrimeField>::from_repr(private_key.to_be_bytes().into())
416            .into_option()
417            .ok_or_else(|| fmt_err!("invalid private key scalar"))?;
418    if bool::from(d_scalar.is_zero()) {
419        return Err(fmt_err!("private key cannot be 0"));
420    }
421
422    let k_scalar: Scalar =
423        <Scalar as k256::elliptic_curve::PrimeField>::from_repr(nonce.to_be_bytes().into())
424            .into_option()
425            .ok_or_else(|| fmt_err!("invalid nonce scalar"))?;
426    if bool::from(k_scalar.is_zero()) {
427        return Err(fmt_err!("nonce cannot be 0"));
428    }
429
430    let mut z = [0u8; 32];
431    z.copy_from_slice(digest.as_slice());
432    let z_fb: FieldBytes = FieldBytes::from(z);
433
434    // Hazmat signing using the scalar `d` (SignPrimitive is implemented for `Scalar`)
435    // Note: returns (Signature, Option<RecoveryId>)
436    let (sig_raw, recid_opt) =
437        <Scalar as hazmat::SignPrimitive<k256::Secp256k1>>::try_sign_prehashed(
438            &d_scalar, k_scalar, &z_fb,
439        )
440        .map_err(|e| fmt_err!("sign_prehashed failed: {e}"))?;
441
442    // Enforce low-s; if mirrored, parity flips (we’ll account for it below if we use recid)
443    let (sig_low, flipped) =
444        if let Some(norm) = sig_raw.normalize_s() { (norm, true) } else { (sig_raw, false) };
445
446    let r_u256 = U256::from_be_bytes(sig_low.r().to_bytes().into());
447    let s_u256 = U256::from_be_bytes(sig_low.s().to_bytes().into());
448
449    // Determine v parity in {0,1}
450    let v_parity = if let Some(id) = recid_opt {
451        let mut v = id.to_byte() & 1;
452        if flipped {
453            v ^= 1;
454        }
455        v
456    } else {
457        // Fallback: choose parity by recovery to expected address
458        let expected_addr = {
459            let sk: SigningKey = parse_private_key(private_key)?;
460            alloy_signer::utils::secret_key_to_address(&sk)
461        };
462        // Try v = 0
463        let cand0 = alloy_primitives::Signature::new(r_u256, s_u256, false);
464        if cand0.recover_address_from_prehash(digest).ok() == Some(expected_addr) {
465            return Ok(cand0);
466        }
467        // Try v = 1
468        let cand1 = alloy_primitives::Signature::new(r_u256, s_u256, true);
469        if cand1.recover_address_from_prehash(digest).ok() == Some(expected_addr) {
470            return Ok(cand1);
471        }
472        return Err(fmt_err!("failed to determine recovery id for signature"));
473    };
474
475    let y_parity = v_parity != 0;
476    Ok(alloy_primitives::Signature::new(r_u256, s_u256, y_parity))
477}
478
479fn sign_with_wallet<FEN: FoundryEvmNetwork>(
480    state: &mut Cheatcodes<FEN>,
481    signer: Option<Address>,
482    digest: &B256,
483) -> Result<alloy_primitives::Signature> {
484    if state.wallets().is_empty() {
485        bail!("no wallets available");
486    }
487
488    let mut wallets = state.wallets().inner.lock();
489    let maybe_provided_sender = wallets.provided_sender;
490    let signers = wallets.multi_wallet.signers()?;
491
492    let signer = if let Some(signer) = signer {
493        signer
494    } else if let Some(provided_sender) = maybe_provided_sender {
495        provided_sender
496    } else if signers.len() == 1 {
497        *signers.keys().next().unwrap()
498    } else {
499        bail!(
500            "could not determine signer, there are multiple signers available use vm.sign(signer, digest) to specify one"
501        );
502    };
503
504    let wallet = signers
505        .get(&signer)
506        .ok_or_else(|| fmt_err!("signer with address {signer} is not available"))?;
507
508    let sig = foundry_common::block_on(wallet.sign_hash(digest))?;
509    debug_assert_eq!(sig.recover_address_from_prehash(digest)?, signer);
510    Ok(sig)
511}
512
513fn sign_p256(private_key: &U256, digest: &B256) -> Result {
514    let signing_key = parse_private_key_p256(private_key)?;
515    let signature: P256Signature = signing_key.sign_prehash(digest.as_slice())?;
516    let signature = signature.normalize_s().unwrap_or(signature);
517    let r_bytes: [u8; 32] = signature.r().to_bytes().into();
518    let s_bytes: [u8; 32] = signature.s().to_bytes().into();
519
520    Ok((r_bytes, s_bytes).abi_encode())
521}
522
523fn parse_affine_point(x: &U256, y: &U256, name: &str) -> Result<AffinePoint> {
524    if x.is_zero() && y.is_zero() {
525        return Ok(AffinePoint::IDENTITY);
526    }
527
528    let encoded = EncodedPoint::from_affine_coordinates(
529        &FieldBytes::from(x.to_be_bytes()),
530        &FieldBytes::from(y.to_be_bytes()),
531        false,
532    );
533    AffinePoint::from_encoded_point(&encoded)
534        .into_option()
535        .ok_or_else(|| fmt_err!("invalid secp256k1 {name}"))
536}
537
538fn parse_projective_point(x: &U256, y: &U256, z: &U256, name: &str) -> Result<ProjectivePoint> {
539    let x_field = parse_field_element(x, name)?;
540    let y_field = parse_field_element(y, name)?;
541    let z_field = parse_field_element(z, name)?;
542
543    if bool::from(z_field.is_zero()) {
544        ensure!(
545            bool::from(x_field.is_zero()) && !bool::from(y_field.is_zero()),
546            "invalid secp256k1 {name}"
547        );
548        return Ok(ProjectivePoint::IDENTITY);
549    }
550
551    let z_inv = z_field.invert().expect("non-zero field element is invertible");
552    let affine_x = U256::from_be_slice(&(x_field * z_inv).to_bytes());
553    let affine_y = U256::from_be_slice(&(y_field * z_inv).to_bytes());
554
555    Ok(ProjectivePoint::from(parse_affine_point(&affine_x, &affine_y, name)?))
556}
557
558fn parse_field_element(value: &U256, name: &str) -> Result<FieldElement> {
559    FieldElement::from_bytes(&FieldBytes::from(value.to_be_bytes()))
560        .into_option()
561        .ok_or_else(|| fmt_err!("invalid secp256k1 {name}"))
562}
563
564fn reduce_ec_scalar(scalar: &U256) -> Scalar {
565    <Scalar as Reduce<K256U256>>::reduce_bytes(&scalar.to_be_bytes().into())
566}
567
568fn encode_affine_point(point: ProjectivePoint) -> Result {
569    if bool::from(point.is_identity()) {
570        return Ok((U256::ZERO, U256::ZERO).abi_encode());
571    }
572
573    let encoded = point.to_affine().to_encoded_point(false);
574    let x = U256::from_be_slice(encoded.x().expect("non-identity point has x coordinate"));
575    let y = U256::from_be_slice(encoded.y().expect("non-identity point has y coordinate"));
576
577    Ok((x, y).abi_encode())
578}
579
580fn encode_projective_point(point: ProjectivePoint) -> Result {
581    if bool::from(point.is_identity()) {
582        return Ok((U256::ZERO, U256::from(1), U256::ZERO).abi_encode());
583    }
584
585    let encoded = point.to_affine().to_encoded_point(false);
586    let x = U256::from_be_slice(encoded.x().expect("non-identity point has x coordinate"));
587    let y = U256::from_be_slice(encoded.y().expect("non-identity point has y coordinate"));
588
589    Ok((x, y, U256::from(1)).abi_encode())
590}
591
592fn validate_private_key<C: ecdsa::PrimeCurve>(private_key: &U256) -> Result<()> {
593    ensure!(*private_key != U256::ZERO, "private key cannot be 0");
594    let order = U256::from_be_slice(&C::ORDER.to_be_byte_array());
595    ensure!(
596        *private_key < order,
597        "private key must be less than the {curve:?} curve order ({order})",
598        curve = C::default(),
599    );
600
601    Ok(())
602}
603
604fn parse_private_key(private_key: &U256) -> Result<SigningKey> {
605    validate_private_key::<k256::Secp256k1>(private_key)?;
606    Ok(SigningKey::from_bytes((&private_key.to_be_bytes()).into())?)
607}
608
609fn parse_private_key_p256(private_key: &U256) -> Result<P256SigningKey> {
610    validate_private_key::<p256::NistP256>(private_key)?;
611    Ok(P256SigningKey::from_bytes((&private_key.to_be_bytes()).into())?)
612}
613
614fn parse_signing_key_ed25519(private_key: &B256) -> Result<Ed25519SigningKey> {
615    Ed25519SigningKey::try_from(private_key.as_slice())
616        .map_err(|e| fmt_err!("invalid Ed25519 private key: {e}"))
617}
618
619fn create_ed25519_key(salt: &B256) -> Result {
620    let signing_key = parse_signing_key_ed25519(salt)?;
621    let public_key = B256::from_slice(signing_key.verification_key().as_ref());
622    Ok((public_key, *salt).abi_encode())
623}
624
625fn public_key_ed25519(private_key: &B256) -> Result {
626    let signing_key = parse_signing_key_ed25519(private_key)?;
627    Ok(B256::from_slice(signing_key.verification_key().as_ref()).abi_encode())
628}
629
630fn sign_ed25519(namespace: &[u8], message: &[u8], private_key: &B256) -> Result {
631    let signing_key = parse_signing_key_ed25519(private_key)?;
632    let combined = [namespace, message].concat();
633    let signature: [u8; 64] = signing_key.sign(&combined).into();
634    Ok(signature.to_vec().abi_encode())
635}
636
637fn verify_ed25519(signature: &[u8], namespace: &[u8], message: &[u8], public_key: &B256) -> Result {
638    if signature.len() != 64 {
639        return Ok(false.abi_encode());
640    }
641
642    let Ok(verification_key) = Ed25519VerificationKey::try_from(public_key.as_slice()) else {
643        return Ok(false.abi_encode());
644    };
645
646    let Ok(sig_bytes): Result<[u8; 64], _> = signature.try_into() else {
647        return Ok(false.abi_encode());
648    };
649
650    let combined = [namespace, message].concat();
651    let valid = verification_key.verify(&Ed25519Signature::from(sig_bytes), &combined).is_ok();
652    Ok(valid.abi_encode())
653}
654
655pub(super) fn parse_wallet(private_key: &U256) -> Result<PrivateKeySigner> {
656    parse_private_key(private_key).map(PrivateKeySigner::from)
657}
658
659pub(super) fn with_private_key_signer<FEN: FoundryEvmNetwork, R>(
660    state: &mut Cheatcodes<FEN>,
661    private_key: &U256,
662    f: impl FnOnce(&PrivateKeySigner) -> Result<R>,
663) -> Result<R> {
664    if !state.private_key_signers.contains_key(private_key)
665        && state.private_key_signers.len() < PRIVATE_KEY_SIGNER_CACHE_LIMIT
666    {
667        let wallet = parse_wallet(private_key)?;
668        state.private_key_signers.insert(*private_key, wallet);
669    }
670
671    if let Some(wallet) = state.private_key_signers.get(private_key) {
672        f(wallet)
673    } else {
674        let wallet = parse_wallet(private_key)?;
675        f(&wallet)
676    }
677}
678
679fn derive_key_str(mnemonic: &str, path: &str, index: u32, language: &str) -> Result {
680    let private_key = derive_private_key_with_language(mnemonic, path, index, language)
681        .map_err(|e| fmt_err!("{e}"))?;
682    Ok(private_key.abi_encode())
683}
684
685fn derive_key<W: Wordlist>(mnemonic: &str, path: &str, index: u32) -> Result {
686    let private_key =
687        derive_private_key::<W>(mnemonic, path, index).map_err(|e| fmt_err!("{e}"))?;
688    Ok(private_key.abi_encode())
689}
690
691fn derive_wallets_str(
692    mnemonic: &str,
693    path: &str,
694    language: &str,
695    count: u32,
696) -> Result<Vec<LocalSigner<SigningKey>>> {
697    match language {
698        "chinese_simplified" => derive_wallets::<ChineseSimplified>(mnemonic, path, count),
699        "chinese_traditional" => derive_wallets::<ChineseTraditional>(mnemonic, path, count),
700        "czech" => derive_wallets::<Czech>(mnemonic, path, count),
701        "english" => derive_wallets::<English>(mnemonic, path, count),
702        "french" => derive_wallets::<French>(mnemonic, path, count),
703        "italian" => derive_wallets::<Italian>(mnemonic, path, count),
704        "japanese" => derive_wallets::<Japanese>(mnemonic, path, count),
705        "korean" => derive_wallets::<Korean>(mnemonic, path, count),
706        "portuguese" => derive_wallets::<Portuguese>(mnemonic, path, count),
707        "spanish" => derive_wallets::<Spanish>(mnemonic, path, count),
708        _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")),
709    }
710}
711
712fn derive_wallets<W: Wordlist>(
713    mnemonic: &str,
714    path: &str,
715    count: u32,
716) -> Result<Vec<LocalSigner<SigningKey>>> {
717    foundry_common::wallet::validate_bip32_path(path).map_err(|e| fmt_err!("{e}"))?;
718
719    let mut wallets = Vec::with_capacity(count as usize);
720    for idx in 0..count {
721        let full_path = foundry_common::wallet::derive_key_path_checked(path, idx)
722            .map_err(|e| fmt_err!("{e}"))?;
723        let wallet =
724            MnemonicBuilder::<W>::default().phrase(mnemonic).derivation_path(full_path)?.build()?;
725        wallets.push(wallet);
726    }
727
728    Ok(wallets)
729}
730
731#[cfg(test)]
732mod tests {
733    use super::*;
734    use alloy_primitives::{FixedBytes, hex::FromHex};
735    use alloy_sol_types::SolCall;
736    use k256::elliptic_curve::Curve;
737    use p256::ecdsa::signature::hazmat::PrehashVerifier;
738    use tempo_contracts::precompiles::{IAccountKeychain, ISignatureVerifier};
739    use tempo_hardfork::TempoHardfork;
740    use tempo_precompiles::{
741        Precompile,
742        account_keychain::{AccountKeychain, KeyRestrictions, SignatureType},
743        signature_verifier::SignatureVerifier,
744        storage::{StorageCtx, hashmap::HashMapStorageProvider},
745    };
746
747    #[test]
748    fn test_sign_p256() {
749        use p256::ecdsa::VerifyingKey;
750
751        let pk_u256: U256 = "1".parse().unwrap();
752        let signing_key = P256SigningKey::from_bytes(&pk_u256.to_be_bytes().into()).unwrap();
753        let digest = FixedBytes::from_hex(
754            "0x44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56",
755        )
756        .unwrap();
757
758        let result = sign_p256(&pk_u256, &digest).unwrap();
759        let result_bytes: [u8; 64] = result.try_into().unwrap();
760        let signature = P256Signature::from_bytes(&result_bytes.into()).unwrap();
761        let verifying_key = VerifyingKey::from(&signing_key);
762        assert!(verifying_key.verify_prehash(digest.as_slice(), &signature).is_ok());
763    }
764
765    #[test]
766    fn test_sign_p256_pk_too_large() {
767        // max n from https://neuromancer.sk/std/secg/secp256r1
768        let pk =
769            "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551".parse().unwrap();
770        let digest = FixedBytes::from_hex(
771            "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad",
772        )
773        .unwrap();
774        let result = sign_p256(&pk, &digest);
775        assert_eq!(
776            result.err().unwrap().to_string(),
777            "private key must be less than the NistP256 curve order (115792089210356248762697446949407573529996955224135760342422259061068512044369)"
778        );
779    }
780
781    #[test]
782    fn test_sign_p256_pk_0() {
783        let digest = FixedBytes::from_hex(
784            "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad",
785        )
786        .unwrap();
787        let result = sign_p256(&U256::ZERO, &digest);
788        assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0");
789    }
790
791    #[test]
792    fn test_sign_with_nonce_varies_and_recovers() {
793        // Given a fixed private key and digest
794        let pk_u256: U256 = U256::from(1u64);
795        let digest = FixedBytes::from_hex(
796            "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
797        )
798        .unwrap();
799
800        // Two distinct nonces
801        let n1: U256 = U256::from(123u64);
802        let n2: U256 = U256::from(456u64);
803
804        // Sign with both nonces
805        let sig1 = sign_with_nonce(&pk_u256, &digest, &n1).expect("sig1");
806        let sig2 = sign_with_nonce(&pk_u256, &digest, &n2).expect("sig2");
807
808        // (r,s) must differ when nonce differs
809        assert!(
810            sig1.r() != sig2.r() || sig1.s() != sig2.s(),
811            "signatures should differ with different nonces"
812        );
813
814        // ecrecover must yield the address for both signatures
815        let sk = parse_private_key(&pk_u256).unwrap();
816        let expected = alloy_signer::utils::secret_key_to_address(&sk);
817
818        assert_eq!(sig1.recover_address_from_prehash(&digest).unwrap(), expected);
819        assert_eq!(sig2.recover_address_from_prehash(&digest).unwrap(), expected);
820    }
821
822    #[test]
823    fn test_sign_with_nonce_zero_nonce_errors() {
824        // nonce = 0 should be rejected
825        let pk_u256: U256 = U256::from(1u64);
826        let digest = FixedBytes::from_hex(
827            "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
828        )
829        .unwrap();
830        let n0: U256 = U256::ZERO;
831
832        let err = sign_with_nonce(&pk_u256, &digest, &n0).unwrap_err();
833        let msg = err.to_string();
834        assert!(msg.contains("nonce cannot be 0"), "unexpected error: {msg}");
835    }
836
837    #[test]
838    fn test_sign_with_nonce_nonce_ge_order_errors() {
839        // nonce >= n should be rejected
840        use k256::Secp256k1;
841        // Curve order n as U256
842        let n_u256 = U256::from_be_slice(&Secp256k1::ORDER.to_be_byte_array());
843
844        let pk_u256: U256 = U256::from(1u64);
845        let digest = FixedBytes::from_hex(
846            "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
847        )
848        .unwrap();
849
850        // Try exactly n (>= n invalid)
851        let err = sign_with_nonce(&pk_u256, &digest, &n_u256).unwrap_err();
852        let msg = err.to_string();
853        assert!(msg.contains("invalid nonce scalar"), "unexpected error: {msg}");
854    }
855
856    #[test]
857    fn test_sign_keychain_encodes_v2_signature_for_account() {
858        let private_key = U256::from(0xB0Bu64);
859        let account = Address::repeat_byte(0x11);
860        let digest = B256::from([0x22; 32]);
861        let mut state = Cheatcodes::default();
862
863        let result = sign_keychain(&mut state, &private_key, &account, &digest).unwrap();
864        let signature = Vec::<u8>::abi_decode(&result).unwrap();
865
866        assert_eq!(signature.len(), 86);
867        assert_eq!(signature[0], 0x04);
868        assert_eq!(Address::from_slice(&signature[1..21]), account);
869
870        let parsed = TempoSignature::from_bytes(&signature).unwrap();
871        assert!(parsed.is_v2_keychain());
872
873        let keychain = parsed.as_keychain().unwrap();
874        let expected_key = parse_wallet(&private_key).unwrap().address();
875        assert_eq!(keychain.user_address, account);
876        assert_eq!(keychain.key_id(&digest).unwrap(), expected_key);
877    }
878
879    #[test]
880    fn private_key_signers_are_cached_for_repeated_lookup() {
881        let private_key = U256::from(0xB0Bu64);
882        let mut state = Cheatcodes::default();
883
884        let first =
885            with_private_key_signer(&mut state, &private_key, |wallet| Ok(wallet.address()))
886                .unwrap();
887        assert_eq!(state.private_key_signers.len(), 1);
888
889        let second =
890            with_private_key_signer(&mut state, &private_key, |wallet| Ok(wallet.address()))
891                .unwrap();
892        assert_eq!(state.private_key_signers.len(), 1);
893        assert_eq!(first, second);
894    }
895
896    #[test]
897    fn test_sign_keychain_matches_t6_signature_verifier_state() {
898        let root_pk = U256::from(0xA11CEu64);
899        let access_pk = U256::from(0xB0Bu64);
900        let admin_pk = U256::from(0xC0FFEEu64);
901        let revoked_pk = U256::from(0xBADu64);
902        let expired_pk = U256::from(0xE441u64);
903        let unknown_pk = U256::from(0xFACEu64);
904
905        let root = parse_wallet(&root_pk).unwrap().address();
906        let access_key = parse_wallet(&access_pk).unwrap().address();
907        let admin_key = parse_wallet(&admin_pk).unwrap().address();
908        let revoked_key = parse_wallet(&revoked_pk).unwrap().address();
909        let expired_key = parse_wallet(&expired_pk).unwrap().address();
910
911        let hash = B256::from([0x44; 32]);
912        let admin_hash = B256::from([0x66; 32]);
913
914        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T6);
915        storage.set_timestamp(U256::from(1_000u64));
916        StorageCtx::enter(&mut storage, || {
917            let mut keychain = AccountKeychain::new();
918            keychain.initialize()?;
919            keychain.set_tx_origin(root)?;
920
921            authorize_t6_access_key(&mut keychain, root, access_key, u64::MAX)?;
922            authorize_t6_access_key(&mut keychain, root, revoked_key, u64::MAX)?;
923            authorize_t6_access_key(&mut keychain, root, expired_key, 1_005)?;
924            keychain.authorize_admin_key(root, admin_key, SignatureType::Secp256k1, None)?;
925            keychain.revoke_key(root, IAccountKeychain::revokeKeyCall { keyId: revoked_key })?;
926
927            assert!(verify_keychain(root, hash, keychain_signature(&access_pk, root, hash)));
928            assert!(!verify_keychain(root, hash, keychain_signature(&revoked_pk, root, hash)));
929            assert!(!verify_keychain(root, hash, keychain_signature(&unknown_pk, root, hash)));
930            assert!(!verify_keychain(
931                Address::repeat_byte(0x99),
932                hash,
933                keychain_signature(&access_pk, root, hash)
934            ));
935            assert!(verify_keychain_admin(
936                root,
937                admin_hash,
938                keychain_signature(&admin_pk, root, admin_hash)
939            ));
940            assert!(verify_keychain_admin(
941                root,
942                admin_hash,
943                keychain_signature(&root_pk, root, admin_hash)
944            ));
945            assert!(!verify_keychain_admin(
946                root,
947                admin_hash,
948                keychain_signature(&access_pk, root, admin_hash)
949            ));
950            assert!(!verify_keychain_admin(
951                Address::repeat_byte(0x88),
952                admin_hash,
953                keychain_signature(&admin_pk, root, admin_hash)
954            ));
955            assert_keychain_signature_reverts(root, hash, vec![0x04]);
956
957            Ok::<_, eyre::Report>(())
958        })
959        .unwrap();
960
961        storage.set_timestamp(U256::from(1_006u64));
962        StorageCtx::enter(&mut storage, || {
963            assert!(!verify_keychain(root, hash, keychain_signature(&expired_pk, root, hash)));
964            Ok::<_, eyre::Report>(())
965        })
966        .unwrap();
967    }
968
969    fn authorize_t6_access_key(
970        keychain: &mut AccountKeychain,
971        account: Address,
972        key_id: Address,
973        expiry: u64,
974    ) -> eyre::Result<()> {
975        keychain.authorize_key(
976            account,
977            key_id,
978            SignatureType::Secp256k1,
979            KeyRestrictions {
980                expiry,
981                enforceLimits: false,
982                limits: vec![],
983                allowAnyCalls: true,
984                allowedCalls: vec![],
985            },
986            None,
987        )?;
988        Ok(())
989    }
990
991    fn keychain_signature(private_key: &U256, account: Address, hash: B256) -> Vec<u8> {
992        let mut state = Cheatcodes::default();
993        Vec::<u8>::abi_decode(&sign_keychain(&mut state, private_key, &account, &hash).unwrap())
994            .unwrap()
995    }
996
997    fn verify_keychain(account: Address, hash: B256, signature: Vec<u8>) -> bool {
998        let calldata =
999            ISignatureVerifier::verifyKeychainCall { account, hash, signature: signature.into() }
1000                .abi_encode();
1001
1002        let output = SignatureVerifier::new().call(&calldata, Address::ZERO).unwrap();
1003        assert!(!output.is_revert(), "verifyKeychain reverted: {:?}", output.bytes);
1004        ISignatureVerifier::verifyKeychainCall::abi_decode_returns(&output.bytes).unwrap()
1005    }
1006
1007    fn verify_keychain_admin(account: Address, hash: B256, signature: Vec<u8>) -> bool {
1008        let calldata = ISignatureVerifier::verifyKeychainAdminCall {
1009            account,
1010            hash,
1011            signature: signature.into(),
1012        }
1013        .abi_encode();
1014
1015        let output = SignatureVerifier::new().call(&calldata, Address::ZERO).unwrap();
1016        assert!(!output.is_revert(), "verifyKeychainAdmin reverted: {:?}", output.bytes);
1017        ISignatureVerifier::verifyKeychainAdminCall::abi_decode_returns(&output.bytes).unwrap()
1018    }
1019
1020    fn assert_keychain_signature_reverts(account: Address, hash: B256, signature: Vec<u8>) {
1021        let calldata =
1022            ISignatureVerifier::verifyKeychainCall { account, hash, signature: signature.into() }
1023                .abi_encode();
1024
1025        let output = SignatureVerifier::new().call(&calldata, Address::ZERO).unwrap();
1026        assert!(output.is_revert(), "malformed keychain signature should revert");
1027    }
1028
1029    #[test]
1030    fn test_create_ed25519_key_determinism() {
1031        let salt = B256::from([1u8; 32]);
1032        let result1 = create_ed25519_key(&salt).unwrap();
1033        let result2 = create_ed25519_key(&salt).unwrap();
1034        assert_eq!(result1, result2, "same salt should produce same keys");
1035    }
1036
1037    #[test]
1038    fn test_create_ed25519_key_different_salts() {
1039        let salt1 = B256::from([1u8; 32]);
1040        let salt2 = B256::from([2u8; 32]);
1041        let result1 = create_ed25519_key(&salt1).unwrap();
1042        let result2 = create_ed25519_key(&salt2).unwrap();
1043        assert_ne!(result1, result2, "different salts should produce different keys");
1044    }
1045
1046    #[test]
1047    fn test_public_key_ed25519_consistency() {
1048        let salt = B256::from([42u8; 32]);
1049        let create_result = create_ed25519_key(&salt).unwrap();
1050        let (expected_public, private): (B256, B256) =
1051            <(B256, B256)>::abi_decode(&create_result).unwrap();
1052
1053        let derived_public_result = public_key_ed25519(&private).unwrap();
1054        let derived_public = B256::abi_decode(&derived_public_result).unwrap();
1055
1056        assert_eq!(expected_public, derived_public, "derived public key should match");
1057    }
1058
1059    #[test]
1060    fn test_sign_and_verify_ed25519_valid() {
1061        let salt = B256::from([123u8; 32]);
1062        let create_result = create_ed25519_key(&salt).unwrap();
1063        let (public_key, private_key): (B256, B256) =
1064            <(B256, B256)>::abi_decode(&create_result).unwrap();
1065
1066        let namespace = b"test.namespace";
1067        let message = b"hello world";
1068        let sig_result = sign_ed25519(namespace, message, &private_key).unwrap();
1069        let sig_bytes: Vec<u8> = Vec::abi_decode(&sig_result).unwrap();
1070
1071        let verify_result = verify_ed25519(&sig_bytes, namespace, message, &public_key).unwrap();
1072        let valid = bool::abi_decode(&verify_result).unwrap();
1073
1074        assert!(valid, "signature should be valid");
1075    }
1076
1077    #[test]
1078    fn test_verify_ed25519_invalid_signature() {
1079        let salt = B256::from([123u8; 32]);
1080        let create_result = create_ed25519_key(&salt).unwrap();
1081        let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap();
1082
1083        let invalid_sig = [0u8; 64];
1084        let namespace = b"test.namespace";
1085        let message = b"hello world";
1086
1087        let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap();
1088        let valid = bool::abi_decode(&verify_result).unwrap();
1089
1090        assert!(!valid, "invalid signature should not verify");
1091    }
1092
1093    #[test]
1094    fn test_verify_ed25519_namespace_separation() {
1095        let salt = B256::from([123u8; 32]);
1096        let create_result = create_ed25519_key(&salt).unwrap();
1097        let (public_key, private_key): (B256, B256) =
1098            <(B256, B256)>::abi_decode(&create_result).unwrap();
1099
1100        let namespace_a = b"namespace.a";
1101        let message = b"message";
1102        let sig_result = sign_ed25519(namespace_a, message, &private_key).unwrap();
1103        let sig_bytes: Vec<u8> = Vec::abi_decode(&sig_result).unwrap();
1104
1105        let namespace_b = b"namespace.b";
1106        let verify_result = verify_ed25519(&sig_bytes, namespace_b, message, &public_key).unwrap();
1107        let valid = bool::abi_decode(&verify_result).unwrap();
1108        assert!(!valid, "signature with namespace A should not verify with namespace B");
1109
1110        let verify_result = verify_ed25519(&sig_bytes, namespace_a, message, &public_key).unwrap();
1111        let valid = bool::abi_decode(&verify_result).unwrap();
1112        assert!(valid, "signature should verify with correct namespace");
1113    }
1114
1115    #[test]
1116    fn test_verify_ed25519_invalid_signature_length() {
1117        let salt = B256::from([123u8; 32]);
1118        let create_result = create_ed25519_key(&salt).unwrap();
1119        let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap();
1120
1121        let invalid_sig = [0u8; 32];
1122        let namespace = b"test";
1123        let message = b"message";
1124
1125        let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap();
1126        let valid = bool::abi_decode(&verify_result).unwrap();
1127        assert!(!valid, "signature with wrong length should not verify");
1128    }
1129
1130    const MNEMONIC: &str = "test test test test test test test test test test test junk";
1131
1132    #[test]
1133    fn derive_key_rejects_harden_bit_overflow() {
1134        let err = derive_key::<English>(MNEMONIC, "m/44'/60'/0'/0/2147483648'", 0)
1135            .unwrap_err()
1136            .to_string();
1137        assert!(err.contains("harden bit"), "{err}");
1138
1139        let err = derive_key::<English>(MNEMONIC, "m/44'/60'/0'/0/2147483648h", 0)
1140            .unwrap_err()
1141            .to_string();
1142        assert!(err.contains("harden bit"), "{err}");
1143
1144        let err =
1145            derive_key::<English>(MNEMONIC, "m/44'/60'/0'/0", foundry_common::wallet::BIP32_HARDEN)
1146                .unwrap_err()
1147                .to_string();
1148        assert!(err.contains("harden bit"), "{err}");
1149
1150        assert!(derive_key::<English>(MNEMONIC, "m/44'/60'/0'/0", 0).is_ok());
1151        assert!(
1152            derive_key::<English>(
1153                MNEMONIC,
1154                &format!("m/44'/60'/0'/0/{}", foundry_common::wallet::BIP32_HARDEN - 1),
1155                0
1156            )
1157            .is_ok()
1158        );
1159    }
1160
1161    #[test]
1162    fn remember_keys_rejects_harden_bit_overflow() {
1163        let err = derive_wallets::<English>(MNEMONIC, "m/44'/60'/0'/0/2147483648'", 1)
1164            .unwrap_err()
1165            .to_string();
1166        assert!(err.contains("harden bit"), "{err}");
1167        assert!(derive_wallets::<English>(MNEMONIC, "m/44'/60'/0'/0", 1).is_ok());
1168    }
1169}