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 k256::{
15    ecdsa::SigningKey,
16    elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint},
17};
18use p256::ecdsa::{
19    Signature as P256Signature, SigningKey as P256SigningKey, signature::hazmat::PrehashSigner,
20};
21
22/// The BIP32 default derivation path prefix.
23const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/";
24
25impl Cheatcode for createWallet_0Call {
26    fn apply(&self, state: &mut Cheatcodes) -> Result {
27        let Self { walletLabel } = self;
28        create_wallet(&U256::from_be_bytes(keccak256(walletLabel).0), Some(walletLabel), state)
29    }
30}
31
32impl Cheatcode for createWallet_1Call {
33    fn apply(&self, state: &mut Cheatcodes) -> Result {
34        let Self { privateKey } = self;
35        create_wallet(privateKey, None, state)
36    }
37}
38
39impl Cheatcode for createWallet_2Call {
40    fn apply(&self, state: &mut Cheatcodes) -> Result {
41        let Self { privateKey, walletLabel } = self;
42        create_wallet(privateKey, Some(walletLabel), state)
43    }
44}
45
46impl Cheatcode for sign_0Call {
47    fn apply(&self, _state: &mut Cheatcodes) -> Result {
48        let Self { wallet, digest } = self;
49        let sig = sign(&wallet.privateKey, digest)?;
50        Ok(encode_full_sig(sig))
51    }
52}
53
54impl Cheatcode for signCompact_0Call {
55    fn apply(&self, _state: &mut Cheatcodes) -> Result {
56        let Self { wallet, digest } = self;
57        let sig = sign(&wallet.privateKey, digest)?;
58        Ok(encode_compact_sig(sig))
59    }
60}
61
62impl Cheatcode for deriveKey_0Call {
63    fn apply(&self, _state: &mut Cheatcodes) -> Result {
64        let Self { mnemonic, index } = self;
65        derive_key::<English>(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index)
66    }
67}
68
69impl Cheatcode for deriveKey_1Call {
70    fn apply(&self, _state: &mut Cheatcodes) -> Result {
71        let Self { mnemonic, derivationPath, index } = self;
72        derive_key::<English>(mnemonic, derivationPath, *index)
73    }
74}
75
76impl Cheatcode for deriveKey_2Call {
77    fn apply(&self, _state: &mut Cheatcodes) -> Result {
78        let Self { mnemonic, index, language } = self;
79        derive_key_str(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index, language)
80    }
81}
82
83impl Cheatcode for deriveKey_3Call {
84    fn apply(&self, _state: &mut Cheatcodes) -> Result {
85        let Self { mnemonic, derivationPath, index, language } = self;
86        derive_key_str(mnemonic, derivationPath, *index, language)
87    }
88}
89
90impl Cheatcode for rememberKeyCall {
91    fn apply(&self, state: &mut Cheatcodes) -> Result {
92        let Self { privateKey } = self;
93        let wallet = parse_wallet(privateKey)?;
94        let address = inject_wallet(state, wallet);
95        Ok(address.abi_encode())
96    }
97}
98
99impl Cheatcode for rememberKeys_0Call {
100    fn apply(&self, state: &mut Cheatcodes) -> Result {
101        let Self { mnemonic, derivationPath, count } = self;
102        let wallets = derive_wallets::<English>(mnemonic, derivationPath, *count)?;
103        let mut addresses = Vec::<Address>::with_capacity(wallets.len());
104        for wallet in wallets {
105            let addr = inject_wallet(state, wallet);
106            addresses.push(addr);
107        }
108
109        Ok(addresses.abi_encode())
110    }
111}
112
113impl Cheatcode for rememberKeys_1Call {
114    fn apply(&self, state: &mut Cheatcodes) -> Result {
115        let Self { mnemonic, derivationPath, language, count } = self;
116        let wallets = derive_wallets_str(mnemonic, derivationPath, language, *count)?;
117        let mut addresses = Vec::<Address>::with_capacity(wallets.len());
118        for wallet in wallets {
119            let addr = inject_wallet(state, wallet);
120            addresses.push(addr);
121        }
122
123        Ok(addresses.abi_encode())
124    }
125}
126
127fn inject_wallet(state: &mut Cheatcodes, wallet: LocalSigner<SigningKey>) -> Address {
128    let address = wallet.address();
129    state.wallets().add_local_signer(wallet);
130    address
131}
132
133impl Cheatcode for sign_1Call {
134    fn apply(&self, _state: &mut Cheatcodes) -> Result {
135        let Self { privateKey, digest } = self;
136        let sig = sign(privateKey, digest)?;
137        Ok(encode_full_sig(sig))
138    }
139}
140
141impl Cheatcode for signCompact_1Call {
142    fn apply(&self, _state: &mut Cheatcodes) -> Result {
143        let Self { privateKey, digest } = self;
144        let sig = sign(privateKey, digest)?;
145        Ok(encode_compact_sig(sig))
146    }
147}
148
149impl Cheatcode for sign_2Call {
150    fn apply(&self, state: &mut Cheatcodes) -> Result {
151        let Self { digest } = self;
152        let sig = sign_with_wallet(state, None, digest)?;
153        Ok(encode_full_sig(sig))
154    }
155}
156
157impl Cheatcode for signCompact_2Call {
158    fn apply(&self, state: &mut Cheatcodes) -> Result {
159        let Self { digest } = self;
160        let sig = sign_with_wallet(state, None, digest)?;
161        Ok(encode_compact_sig(sig))
162    }
163}
164
165impl Cheatcode for sign_3Call {
166    fn apply(&self, state: &mut Cheatcodes) -> Result {
167        let Self { signer, digest } = self;
168        let sig = sign_with_wallet(state, Some(*signer), digest)?;
169        Ok(encode_full_sig(sig))
170    }
171}
172
173impl Cheatcode for signCompact_3Call {
174    fn apply(&self, state: &mut Cheatcodes) -> Result {
175        let Self { signer, digest } = self;
176        let sig = sign_with_wallet(state, Some(*signer), digest)?;
177        Ok(encode_compact_sig(sig))
178    }
179}
180
181impl Cheatcode for signP256Call {
182    fn apply(&self, _state: &mut Cheatcodes) -> Result {
183        let Self { privateKey, digest } = self;
184        sign_p256(privateKey, digest)
185    }
186}
187
188impl Cheatcode for publicKeyP256Call {
189    fn apply(&self, _state: &mut Cheatcodes) -> Result {
190        let Self { privateKey } = self;
191        let pub_key =
192            parse_private_key_p256(privateKey)?.verifying_key().as_affine().to_encoded_point(false);
193        let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into());
194        let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into());
195
196        Ok((pub_key_x, pub_key_y).abi_encode())
197    }
198}
199
200/// Using a given private key, return its public ETH address, its public key affine x and y
201/// coordinates, and its private key (see the 'Wallet' struct)
202///
203/// If 'label' is set to 'Some()', assign that label to the associated ETH address in state
204fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes) -> Result {
205    let key = parse_private_key(private_key)?;
206    let addr = alloy_signer::utils::secret_key_to_address(&key);
207
208    let pub_key = key.verifying_key().as_affine().to_encoded_point(false);
209    let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into());
210    let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into());
211
212    if let Some(label) = label {
213        state.labels.insert(addr, label.into());
214    }
215
216    Ok(Wallet { addr, publicKeyX: pub_key_x, publicKeyY: pub_key_y, privateKey: *private_key }
217        .abi_encode())
218}
219
220fn encode_full_sig(sig: alloy_primitives::Signature) -> Vec<u8> {
221    // Retrieve v, r and s from signature.
222    let v = U256::from(sig.v() as u64 + 27);
223    let r = B256::from(sig.r());
224    let s = B256::from(sig.s());
225    (v, r, s).abi_encode()
226}
227
228fn encode_compact_sig(sig: alloy_primitives::Signature) -> Vec<u8> {
229    // Implement EIP-2098 compact signature.
230    let r = B256::from(sig.r());
231    let mut vs = sig.s();
232    vs.set_bit(255, sig.v());
233    (r, vs).abi_encode()
234}
235
236fn sign(private_key: &U256, digest: &B256) -> Result<alloy_primitives::Signature> {
237    // The `ecrecover` precompile does not use EIP-155. No chain ID is needed.
238    let wallet = parse_wallet(private_key)?;
239    let sig = wallet.sign_hash_sync(digest)?;
240    debug_assert_eq!(sig.recover_address_from_prehash(digest)?, wallet.address());
241    Ok(sig)
242}
243
244fn sign_with_wallet(
245    state: &mut Cheatcodes,
246    signer: Option<Address>,
247    digest: &B256,
248) -> Result<alloy_primitives::Signature> {
249    if state.wallets().is_empty() {
250        bail!("no wallets available");
251    }
252
253    let mut wallets = state.wallets().inner.lock();
254    let maybe_provided_sender = wallets.provided_sender;
255    let signers = wallets.multi_wallet.signers()?;
256
257    let signer = if let Some(signer) = signer {
258        signer
259    } else if let Some(provided_sender) = maybe_provided_sender {
260        provided_sender
261    } else if signers.len() == 1 {
262        *signers.keys().next().unwrap()
263    } else {
264        bail!(
265            "could not determine signer, there are multiple signers available use vm.sign(signer, digest) to specify one"
266        );
267    };
268
269    let wallet = signers
270        .get(&signer)
271        .ok_or_else(|| fmt_err!("signer with address {signer} is not available"))?;
272
273    let sig = foundry_common::block_on(wallet.sign_hash(digest))?;
274    debug_assert_eq!(sig.recover_address_from_prehash(digest)?, signer);
275    Ok(sig)
276}
277
278fn sign_p256(private_key: &U256, digest: &B256) -> Result {
279    let signing_key = parse_private_key_p256(private_key)?;
280    let signature: P256Signature = signing_key.sign_prehash(digest.as_slice())?;
281    let signature = signature.normalize_s().unwrap_or(signature);
282    let r_bytes: [u8; 32] = signature.r().to_bytes().into();
283    let s_bytes: [u8; 32] = signature.s().to_bytes().into();
284
285    Ok((r_bytes, s_bytes).abi_encode())
286}
287
288fn validate_private_key<C: ecdsa::PrimeCurve>(private_key: &U256) -> Result<()> {
289    ensure!(*private_key != U256::ZERO, "private key cannot be 0");
290    let order = U256::from_be_slice(&C::ORDER.to_be_byte_array());
291    ensure!(
292        *private_key < U256::from_be_slice(&C::ORDER.to_be_byte_array()),
293        "private key must be less than the {curve:?} curve order ({order})",
294        curve = C::default(),
295    );
296
297    Ok(())
298}
299
300fn parse_private_key(private_key: &U256) -> Result<SigningKey> {
301    validate_private_key::<k256::Secp256k1>(private_key)?;
302    Ok(SigningKey::from_bytes((&private_key.to_be_bytes()).into())?)
303}
304
305fn parse_private_key_p256(private_key: &U256) -> Result<P256SigningKey> {
306    validate_private_key::<p256::NistP256>(private_key)?;
307    Ok(P256SigningKey::from_bytes((&private_key.to_be_bytes()).into())?)
308}
309
310pub(super) fn parse_wallet(private_key: &U256) -> Result<PrivateKeySigner> {
311    parse_private_key(private_key).map(PrivateKeySigner::from)
312}
313
314fn derive_key_str(mnemonic: &str, path: &str, index: u32, language: &str) -> Result {
315    match language {
316        "chinese_simplified" => derive_key::<ChineseSimplified>(mnemonic, path, index),
317        "chinese_traditional" => derive_key::<ChineseTraditional>(mnemonic, path, index),
318        "czech" => derive_key::<Czech>(mnemonic, path, index),
319        "english" => derive_key::<English>(mnemonic, path, index),
320        "french" => derive_key::<French>(mnemonic, path, index),
321        "italian" => derive_key::<Italian>(mnemonic, path, index),
322        "japanese" => derive_key::<Japanese>(mnemonic, path, index),
323        "korean" => derive_key::<Korean>(mnemonic, path, index),
324        "portuguese" => derive_key::<Portuguese>(mnemonic, path, index),
325        "spanish" => derive_key::<Spanish>(mnemonic, path, index),
326        _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")),
327    }
328}
329
330fn derive_key<W: Wordlist>(mnemonic: &str, path: &str, index: u32) -> Result {
331    fn derive_key_path(path: &str, index: u32) -> String {
332        let mut out = path.to_string();
333        if !out.ends_with('/') {
334            out.push('/');
335        }
336        out.push_str(&index.to_string());
337        out
338    }
339
340    let wallet = MnemonicBuilder::<W>::default()
341        .phrase(mnemonic)
342        .derivation_path(derive_key_path(path, index))?
343        .build()?;
344    let private_key = U256::from_be_bytes(wallet.credential().to_bytes().into());
345    Ok(private_key.abi_encode())
346}
347
348fn derive_wallets_str(
349    mnemonic: &str,
350    path: &str,
351    language: &str,
352    count: u32,
353) -> Result<Vec<LocalSigner<SigningKey>>> {
354    match language {
355        "chinese_simplified" => derive_wallets::<ChineseSimplified>(mnemonic, path, count),
356        "chinese_traditional" => derive_wallets::<ChineseTraditional>(mnemonic, path, count),
357        "czech" => derive_wallets::<Czech>(mnemonic, path, count),
358        "english" => derive_wallets::<English>(mnemonic, path, count),
359        "french" => derive_wallets::<French>(mnemonic, path, count),
360        "italian" => derive_wallets::<Italian>(mnemonic, path, count),
361        "japanese" => derive_wallets::<Japanese>(mnemonic, path, count),
362        "korean" => derive_wallets::<Korean>(mnemonic, path, count),
363        "portuguese" => derive_wallets::<Portuguese>(mnemonic, path, count),
364        "spanish" => derive_wallets::<Spanish>(mnemonic, path, count),
365        _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")),
366    }
367}
368
369fn derive_wallets<W: Wordlist>(
370    mnemonic: &str,
371    path: &str,
372    count: u32,
373) -> Result<Vec<LocalSigner<SigningKey>>> {
374    let mut out = path.to_string();
375
376    if !out.ends_with('/') {
377        out.push('/');
378    }
379
380    let mut wallets = Vec::with_capacity(count as usize);
381    for idx in 0..count {
382        let wallet = MnemonicBuilder::<W>::default()
383            .phrase(mnemonic)
384            .derivation_path(format!("{out}{idx}"))?
385            .build()?;
386        wallets.push(wallet);
387    }
388
389    Ok(wallets)
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395    use alloy_primitives::{FixedBytes, hex::FromHex};
396    use p256::ecdsa::signature::hazmat::PrehashVerifier;
397
398    #[test]
399    fn test_sign_p256() {
400        use p256::ecdsa::VerifyingKey;
401
402        let pk_u256: U256 = "1".parse().unwrap();
403        let signing_key = P256SigningKey::from_bytes(&pk_u256.to_be_bytes().into()).unwrap();
404        let digest = FixedBytes::from_hex(
405            "0x44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56",
406        )
407        .unwrap();
408
409        let result = sign_p256(&pk_u256, &digest).unwrap();
410        let result_bytes: [u8; 64] = result.try_into().unwrap();
411        let signature = P256Signature::from_bytes(&result_bytes.into()).unwrap();
412        let verifying_key = VerifyingKey::from(&signing_key);
413        assert!(verifying_key.verify_prehash(digest.as_slice(), &signature).is_ok());
414    }
415
416    #[test]
417    fn test_sign_p256_pk_too_large() {
418        // max n from https://neuromancer.sk/std/secg/secp256r1
419        let pk =
420            "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551".parse().unwrap();
421        let digest = FixedBytes::from_hex(
422            "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad",
423        )
424        .unwrap();
425        let result = sign_p256(&pk, &digest);
426        assert_eq!(
427            result.err().unwrap().to_string(),
428            "private key must be less than the NistP256 curve order (115792089210356248762697446949407573529996955224135760342422259061068512044369)"
429        );
430    }
431
432    #[test]
433    fn test_sign_p256_pk_0() {
434        let digest = FixedBytes::from_hex(
435            "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad",
436        )
437        .unwrap();
438        let result = sign_p256(&U256::ZERO, &digest);
439        assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0");
440    }
441}