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