Skip to main content

foundry_cheatcodes/
crypto.rs

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