1use 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
36const 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
319fn 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 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 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 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
391fn 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 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 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 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 let expected_addr = {
451 let sk: SigningKey = parse_private_key(private_key)?;
452 alloy_signer::utils::secret_key_to_address(&sk)
453 };
454 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 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 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 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 let pk_u256: U256 = U256::ONE;
916 let digest = FixedBytes::from_hex(
917 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
918 )
919 .unwrap();
920
921 let n1: U256 = U256::from(123u64);
923 let n2: U256 = U256::from(456u64);
924
925 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 assert!(
931 sig1.r() != sig2.r() || sig1.s() != sig2.s(),
932 "signatures should differ with different nonces"
933 );
934
935 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 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 use k256::Secp256k1;
962 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 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}