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