Skip to main content

foundry_cheatcodes/
crypto.rs

1//! Implementations of [`Crypto`](spec::Group::Crypto) Cheatcodes.
2
3use crate::{Cheatcode, Cheatcodes, Result, Vm::*};
4use alloy_primitives::{Address, B256, U256, keccak256};
5use alloy_signer::{Signer, SignerSync};
6use alloy_signer_local::{
7    LocalSigner, MnemonicBuilder, PrivateKeySigner,
8    coins_bip39::{
9        ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, Korean,
10        Portuguese, Spanish, Wordlist,
11    },
12};
13use alloy_sol_types::SolValue;
14use k256::{
15    FieldBytes, Scalar,
16    ecdsa::{SigningKey, hazmat},
17    elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint},
18};
19
20use p256::ecdsa::{
21    Signature as P256Signature, SigningKey as P256SigningKey, signature::hazmat::PrehashSigner,
22};
23
24use ed25519_consensus::{
25    Signature as Ed25519Signature, SigningKey as Ed25519SigningKey,
26    VerificationKey as Ed25519VerificationKey,
27};
28
29/// The BIP32 default derivation path prefix.
30const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/";
31
32impl Cheatcode for createWallet_0Call {
33    fn apply(&self, state: &mut Cheatcodes) -> Result {
34        let Self { walletLabel } = self;
35        create_wallet(&U256::from_be_bytes(keccak256(walletLabel).0), Some(walletLabel), state)
36    }
37}
38
39impl Cheatcode for createWallet_1Call {
40    fn apply(&self, state: &mut Cheatcodes) -> Result {
41        let Self { privateKey } = self;
42        create_wallet(privateKey, None, state)
43    }
44}
45
46impl Cheatcode for createWallet_2Call {
47    fn apply(&self, state: &mut Cheatcodes) -> Result {
48        let Self { privateKey, walletLabel } = self;
49        create_wallet(privateKey, Some(walletLabel), state)
50    }
51}
52
53impl Cheatcode for sign_0Call {
54    fn apply(&self, _state: &mut Cheatcodes) -> Result {
55        let Self { wallet, digest } = self;
56        let sig = sign(&wallet.privateKey, digest)?;
57        Ok(encode_full_sig(sig))
58    }
59}
60
61impl Cheatcode for signWithNonceUnsafeCall {
62    fn apply(&self, _state: &mut Cheatcodes) -> Result {
63        let pk: U256 = self.privateKey;
64        let digest: B256 = self.digest;
65        let nonce: U256 = self.nonce;
66        let sig: alloy_primitives::Signature = sign_with_nonce(&pk, &digest, &nonce)?;
67        Ok(encode_full_sig(sig))
68    }
69}
70
71impl Cheatcode for signCompact_0Call {
72    fn apply(&self, _state: &mut Cheatcodes) -> Result {
73        let Self { wallet, digest } = self;
74        let sig = sign(&wallet.privateKey, digest)?;
75        Ok(encode_compact_sig(sig))
76    }
77}
78
79impl Cheatcode for deriveKey_0Call {
80    fn apply(&self, _state: &mut Cheatcodes) -> Result {
81        let Self { mnemonic, index } = self;
82        derive_key::<English>(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index)
83    }
84}
85
86impl Cheatcode for deriveKey_1Call {
87    fn apply(&self, _state: &mut Cheatcodes) -> Result {
88        let Self { mnemonic, derivationPath, index } = self;
89        derive_key::<English>(mnemonic, derivationPath, *index)
90    }
91}
92
93impl Cheatcode for deriveKey_2Call {
94    fn apply(&self, _state: &mut Cheatcodes) -> Result {
95        let Self { mnemonic, index, language } = self;
96        derive_key_str(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index, language)
97    }
98}
99
100impl Cheatcode for deriveKey_3Call {
101    fn apply(&self, _state: &mut Cheatcodes) -> Result {
102        let Self { mnemonic, derivationPath, index, language } = self;
103        derive_key_str(mnemonic, derivationPath, *index, language)
104    }
105}
106
107impl Cheatcode for rememberKeyCall {
108    fn apply(&self, state: &mut Cheatcodes) -> Result {
109        let Self { privateKey } = self;
110        let wallet = parse_wallet(privateKey)?;
111        let address = inject_wallet(state, wallet);
112        Ok(address.abi_encode())
113    }
114}
115
116impl Cheatcode for rememberKeys_0Call {
117    fn apply(&self, state: &mut Cheatcodes) -> Result {
118        let Self { mnemonic, derivationPath, count } = self;
119        let wallets = derive_wallets::<English>(mnemonic, derivationPath, *count)?;
120        let mut addresses = Vec::<Address>::with_capacity(wallets.len());
121        for wallet in wallets {
122            let addr = inject_wallet(state, wallet);
123            addresses.push(addr);
124        }
125
126        Ok(addresses.abi_encode())
127    }
128}
129
130impl Cheatcode for rememberKeys_1Call {
131    fn apply(&self, state: &mut Cheatcodes) -> Result {
132        let Self { mnemonic, derivationPath, language, count } = self;
133        let wallets = derive_wallets_str(mnemonic, derivationPath, language, *count)?;
134        let mut addresses = Vec::<Address>::with_capacity(wallets.len());
135        for wallet in wallets {
136            let addr = inject_wallet(state, wallet);
137            addresses.push(addr);
138        }
139
140        Ok(addresses.abi_encode())
141    }
142}
143
144fn inject_wallet(state: &mut Cheatcodes, wallet: LocalSigner<SigningKey>) -> Address {
145    let address = wallet.address();
146    state.wallets().add_local_signer(wallet);
147    address
148}
149
150impl Cheatcode for sign_1Call {
151    fn apply(&self, _state: &mut Cheatcodes) -> Result {
152        let Self { privateKey, digest } = self;
153        let sig = sign(privateKey, digest)?;
154        Ok(encode_full_sig(sig))
155    }
156}
157
158impl Cheatcode for signCompact_1Call {
159    fn apply(&self, _state: &mut Cheatcodes) -> Result {
160        let Self { privateKey, digest } = self;
161        let sig = sign(privateKey, digest)?;
162        Ok(encode_compact_sig(sig))
163    }
164}
165
166impl Cheatcode for sign_2Call {
167    fn apply(&self, state: &mut Cheatcodes) -> Result {
168        let Self { digest } = self;
169        let sig = sign_with_wallet(state, None, digest)?;
170        Ok(encode_full_sig(sig))
171    }
172}
173
174impl Cheatcode for signCompact_2Call {
175    fn apply(&self, state: &mut Cheatcodes) -> Result {
176        let Self { digest } = self;
177        let sig = sign_with_wallet(state, None, digest)?;
178        Ok(encode_compact_sig(sig))
179    }
180}
181
182impl Cheatcode for sign_3Call {
183    fn apply(&self, state: &mut Cheatcodes) -> Result {
184        let Self { signer, digest } = self;
185        let sig = sign_with_wallet(state, Some(*signer), digest)?;
186        Ok(encode_full_sig(sig))
187    }
188}
189
190impl Cheatcode for signCompact_3Call {
191    fn apply(&self, state: &mut Cheatcodes) -> Result {
192        let Self { signer, digest } = self;
193        let sig = sign_with_wallet(state, Some(*signer), digest)?;
194        Ok(encode_compact_sig(sig))
195    }
196}
197
198impl Cheatcode for signP256Call {
199    fn apply(&self, _state: &mut Cheatcodes) -> Result {
200        let Self { privateKey, digest } = self;
201        sign_p256(privateKey, digest)
202    }
203}
204
205impl Cheatcode for publicKeyP256Call {
206    fn apply(&self, _state: &mut Cheatcodes) -> Result {
207        let Self { privateKey } = self;
208        let pub_key =
209            parse_private_key_p256(privateKey)?.verifying_key().as_affine().to_encoded_point(false);
210        let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into());
211        let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into());
212
213        Ok((pub_key_x, pub_key_y).abi_encode())
214    }
215}
216
217impl Cheatcode for createEd25519KeyCall {
218    fn apply(&self, _state: &mut Cheatcodes) -> Result {
219        let Self { salt } = self;
220        create_ed25519_key(salt)
221    }
222}
223
224impl Cheatcode for publicKeyEd25519Call {
225    fn apply(&self, _state: &mut Cheatcodes) -> Result {
226        let Self { privateKey } = self;
227        public_key_ed25519(privateKey)
228    }
229}
230
231impl Cheatcode for signEd25519Call {
232    fn apply(&self, _state: &mut Cheatcodes) -> Result {
233        let Self { namespace, message, privateKey } = self;
234        sign_ed25519(namespace, message, privateKey)
235    }
236}
237
238impl Cheatcode for verifyEd25519Call {
239    fn apply(&self, _state: &mut Cheatcodes) -> Result {
240        let Self { signature, namespace, message, publicKey } = self;
241        verify_ed25519(signature, namespace, message, publicKey)
242    }
243}
244
245/// Using a given private key, return its public ETH address, its public key affine x and y
246/// coordinates, and its private key (see the 'Wallet' struct)
247///
248/// If 'label' is set to 'Some()', assign that label to the associated ETH address in state
249fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes) -> Result {
250    let key = parse_private_key(private_key)?;
251    let addr = alloy_signer::utils::secret_key_to_address(&key);
252
253    let pub_key = key.verifying_key().as_affine().to_encoded_point(false);
254    let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into());
255    let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into());
256
257    if let Some(label) = label {
258        state.labels.insert(addr, label.into());
259    }
260
261    Ok(Wallet { addr, publicKeyX: pub_key_x, publicKeyY: pub_key_y, privateKey: *private_key }
262        .abi_encode())
263}
264
265fn encode_full_sig(sig: alloy_primitives::Signature) -> Vec<u8> {
266    // Retrieve v, r and s from signature.
267    let v = U256::from(sig.v() as u64 + 27);
268    let r = B256::from(sig.r());
269    let s = B256::from(sig.s());
270    (v, r, s).abi_encode()
271}
272
273fn encode_compact_sig(sig: alloy_primitives::Signature) -> Vec<u8> {
274    // Implement EIP-2098 compact signature.
275    let r = B256::from(sig.r());
276    let mut vs = sig.s();
277    vs.set_bit(255, sig.v());
278    (r, vs).abi_encode()
279}
280
281fn sign(private_key: &U256, digest: &B256) -> Result<alloy_primitives::Signature> {
282    // The `ecrecover` precompile does not use EIP-155. No chain ID is needed.
283    let wallet = parse_wallet(private_key)?;
284    let sig = wallet.sign_hash_sync(digest)?;
285    debug_assert_eq!(sig.recover_address_from_prehash(digest)?, wallet.address());
286    Ok(sig)
287}
288
289/// Signs `digest` on secp256k1 using a user-supplied ephemeral nonce `k` (no RFC6979).
290/// - `private_key` and `nonce` must be in (0, n)
291/// - `digest` is a 32-byte prehash.
292///
293/// # Warning
294///
295/// Use [`sign_with_nonce`] with extreme caution!
296/// Reusing the same nonce (`k`) with the same private key in ECDSA will leak the private key.
297/// Always generate `nonce` with a cryptographically secure RNG, and never reuse it across
298/// signatures.
299fn sign_with_nonce(
300    private_key: &U256,
301    digest: &B256,
302    nonce: &U256,
303) -> Result<alloy_primitives::Signature> {
304    let d_scalar: Scalar =
305        <Scalar as k256::elliptic_curve::PrimeField>::from_repr(private_key.to_be_bytes().into())
306            .into_option()
307            .ok_or_else(|| fmt_err!("invalid private key scalar"))?;
308    if bool::from(d_scalar.is_zero()) {
309        return Err(fmt_err!("private key cannot be 0"));
310    }
311
312    let k_scalar: Scalar =
313        <Scalar as k256::elliptic_curve::PrimeField>::from_repr(nonce.to_be_bytes().into())
314            .into_option()
315            .ok_or_else(|| fmt_err!("invalid nonce scalar"))?;
316    if bool::from(k_scalar.is_zero()) {
317        return Err(fmt_err!("nonce cannot be 0"));
318    }
319
320    let mut z = [0u8; 32];
321    z.copy_from_slice(digest.as_slice());
322    let z_fb: FieldBytes = FieldBytes::from(z);
323
324    // Hazmat signing using the scalar `d` (SignPrimitive is implemented for `Scalar`)
325    // Note: returns (Signature, Option<RecoveryId>)
326    let (sig_raw, recid_opt) =
327        <Scalar as hazmat::SignPrimitive<k256::Secp256k1>>::try_sign_prehashed(
328            &d_scalar, k_scalar, &z_fb,
329        )
330        .map_err(|e| fmt_err!("sign_prehashed failed: {e}"))?;
331
332    // Enforce low-s; if mirrored, parity flips (we’ll account for it below if we use recid)
333    let (sig_low, flipped) =
334        if let Some(norm) = sig_raw.normalize_s() { (norm, true) } else { (sig_raw, false) };
335
336    let r_u256 = U256::from_be_bytes(sig_low.r().to_bytes().into());
337    let s_u256 = U256::from_be_bytes(sig_low.s().to_bytes().into());
338
339    // Determine v parity in {0,1}
340    let v_parity = if let Some(id) = recid_opt {
341        let mut v = id.to_byte() & 1;
342        if flipped {
343            v ^= 1;
344        }
345        v
346    } else {
347        // Fallback: choose parity by recovery to expected address
348        let expected_addr = {
349            let sk: SigningKey = parse_private_key(private_key)?;
350            alloy_signer::utils::secret_key_to_address(&sk)
351        };
352        // Try v = 0
353        let cand0 = alloy_primitives::Signature::new(r_u256, s_u256, false);
354        if cand0.recover_address_from_prehash(digest).ok() == Some(expected_addr) {
355            return Ok(cand0);
356        }
357        // Try v = 1
358        let cand1 = alloy_primitives::Signature::new(r_u256, s_u256, true);
359        if cand1.recover_address_from_prehash(digest).ok() == Some(expected_addr) {
360            return Ok(cand1);
361        }
362        return Err(fmt_err!("failed to determine recovery id for signature"));
363    };
364
365    let y_parity = v_parity != 0;
366    Ok(alloy_primitives::Signature::new(r_u256, s_u256, y_parity))
367}
368
369fn sign_with_wallet(
370    state: &mut Cheatcodes,
371    signer: Option<Address>,
372    digest: &B256,
373) -> Result<alloy_primitives::Signature> {
374    if state.wallets().is_empty() {
375        bail!("no wallets available");
376    }
377
378    let mut wallets = state.wallets().inner.lock();
379    let maybe_provided_sender = wallets.provided_sender;
380    let signers = wallets.multi_wallet.signers()?;
381
382    let signer = if let Some(signer) = signer {
383        signer
384    } else if let Some(provided_sender) = maybe_provided_sender {
385        provided_sender
386    } else if signers.len() == 1 {
387        *signers.keys().next().unwrap()
388    } else {
389        bail!(
390            "could not determine signer, there are multiple signers available use vm.sign(signer, digest) to specify one"
391        );
392    };
393
394    let wallet = signers
395        .get(&signer)
396        .ok_or_else(|| fmt_err!("signer with address {signer} is not available"))?;
397
398    let sig = foundry_common::block_on(wallet.sign_hash(digest))?;
399    debug_assert_eq!(sig.recover_address_from_prehash(digest)?, signer);
400    Ok(sig)
401}
402
403fn sign_p256(private_key: &U256, digest: &B256) -> Result {
404    let signing_key = parse_private_key_p256(private_key)?;
405    let signature: P256Signature = signing_key.sign_prehash(digest.as_slice())?;
406    let signature = signature.normalize_s().unwrap_or(signature);
407    let r_bytes: [u8; 32] = signature.r().to_bytes().into();
408    let s_bytes: [u8; 32] = signature.s().to_bytes().into();
409
410    Ok((r_bytes, s_bytes).abi_encode())
411}
412
413fn validate_private_key<C: ecdsa::PrimeCurve>(private_key: &U256) -> Result<()> {
414    ensure!(*private_key != U256::ZERO, "private key cannot be 0");
415    let order = U256::from_be_slice(&C::ORDER.to_be_byte_array());
416    ensure!(
417        *private_key < order,
418        "private key must be less than the {curve:?} curve order ({order})",
419        curve = C::default(),
420    );
421
422    Ok(())
423}
424
425fn parse_private_key(private_key: &U256) -> Result<SigningKey> {
426    validate_private_key::<k256::Secp256k1>(private_key)?;
427    Ok(SigningKey::from_bytes((&private_key.to_be_bytes()).into())?)
428}
429
430fn parse_private_key_p256(private_key: &U256) -> Result<P256SigningKey> {
431    validate_private_key::<p256::NistP256>(private_key)?;
432    Ok(P256SigningKey::from_bytes((&private_key.to_be_bytes()).into())?)
433}
434
435fn parse_signing_key_ed25519(private_key: &B256) -> Result<Ed25519SigningKey> {
436    Ed25519SigningKey::try_from(private_key.as_slice())
437        .map_err(|e| fmt_err!("invalid Ed25519 private key: {e}"))
438}
439
440fn create_ed25519_key(salt: &B256) -> Result {
441    let signing_key = parse_signing_key_ed25519(salt)?;
442    let public_key = B256::from_slice(signing_key.verification_key().as_ref());
443    Ok((public_key, *salt).abi_encode())
444}
445
446fn public_key_ed25519(private_key: &B256) -> Result {
447    let signing_key = parse_signing_key_ed25519(private_key)?;
448    Ok(B256::from_slice(signing_key.verification_key().as_ref()).abi_encode())
449}
450
451fn sign_ed25519(namespace: &[u8], message: &[u8], private_key: &B256) -> Result {
452    let signing_key = parse_signing_key_ed25519(private_key)?;
453    let combined = [namespace, message].concat();
454    let signature: [u8; 64] = signing_key.sign(&combined).into();
455    Ok(signature.to_vec().abi_encode())
456}
457
458fn verify_ed25519(signature: &[u8], namespace: &[u8], message: &[u8], public_key: &B256) -> Result {
459    if signature.len() != 64 {
460        return Ok(false.abi_encode());
461    }
462
463    let Ok(verification_key) = Ed25519VerificationKey::try_from(public_key.as_slice()) else {
464        return Ok(false.abi_encode());
465    };
466
467    let Ok(sig_bytes): Result<[u8; 64], _> = signature.try_into() else {
468        return Ok(false.abi_encode());
469    };
470
471    let combined = [namespace, message].concat();
472    let valid = verification_key.verify(&Ed25519Signature::from(sig_bytes), &combined).is_ok();
473    Ok(valid.abi_encode())
474}
475
476pub(super) fn parse_wallet(private_key: &U256) -> Result<PrivateKeySigner> {
477    parse_private_key(private_key).map(PrivateKeySigner::from)
478}
479
480fn derive_key_str(mnemonic: &str, path: &str, index: u32, language: &str) -> Result {
481    match language {
482        "chinese_simplified" => derive_key::<ChineseSimplified>(mnemonic, path, index),
483        "chinese_traditional" => derive_key::<ChineseTraditional>(mnemonic, path, index),
484        "czech" => derive_key::<Czech>(mnemonic, path, index),
485        "english" => derive_key::<English>(mnemonic, path, index),
486        "french" => derive_key::<French>(mnemonic, path, index),
487        "italian" => derive_key::<Italian>(mnemonic, path, index),
488        "japanese" => derive_key::<Japanese>(mnemonic, path, index),
489        "korean" => derive_key::<Korean>(mnemonic, path, index),
490        "portuguese" => derive_key::<Portuguese>(mnemonic, path, index),
491        "spanish" => derive_key::<Spanish>(mnemonic, path, index),
492        _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")),
493    }
494}
495
496fn derive_key<W: Wordlist>(mnemonic: &str, path: &str, index: u32) -> Result {
497    fn derive_key_path(path: &str, index: u32) -> String {
498        let mut out = path.to_string();
499        if !out.ends_with('/') {
500            out.push('/');
501        }
502        out.push_str(&index.to_string());
503        out
504    }
505
506    let wallet = MnemonicBuilder::<W>::default()
507        .phrase(mnemonic)
508        .derivation_path(derive_key_path(path, index))?
509        .build()?;
510    let private_key = U256::from_be_bytes(wallet.credential().to_bytes().into());
511    Ok(private_key.abi_encode())
512}
513
514fn derive_wallets_str(
515    mnemonic: &str,
516    path: &str,
517    language: &str,
518    count: u32,
519) -> Result<Vec<LocalSigner<SigningKey>>> {
520    match language {
521        "chinese_simplified" => derive_wallets::<ChineseSimplified>(mnemonic, path, count),
522        "chinese_traditional" => derive_wallets::<ChineseTraditional>(mnemonic, path, count),
523        "czech" => derive_wallets::<Czech>(mnemonic, path, count),
524        "english" => derive_wallets::<English>(mnemonic, path, count),
525        "french" => derive_wallets::<French>(mnemonic, path, count),
526        "italian" => derive_wallets::<Italian>(mnemonic, path, count),
527        "japanese" => derive_wallets::<Japanese>(mnemonic, path, count),
528        "korean" => derive_wallets::<Korean>(mnemonic, path, count),
529        "portuguese" => derive_wallets::<Portuguese>(mnemonic, path, count),
530        "spanish" => derive_wallets::<Spanish>(mnemonic, path, count),
531        _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")),
532    }
533}
534
535fn derive_wallets<W: Wordlist>(
536    mnemonic: &str,
537    path: &str,
538    count: u32,
539) -> Result<Vec<LocalSigner<SigningKey>>> {
540    let mut out = path.to_string();
541
542    if !out.ends_with('/') {
543        out.push('/');
544    }
545
546    let mut wallets = Vec::with_capacity(count as usize);
547    for idx in 0..count {
548        let wallet = MnemonicBuilder::<W>::default()
549            .phrase(mnemonic)
550            .derivation_path(format!("{out}{idx}"))?
551            .build()?;
552        wallets.push(wallet);
553    }
554
555    Ok(wallets)
556}
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561    use alloy_primitives::{FixedBytes, hex::FromHex};
562    use k256::elliptic_curve::Curve;
563    use p256::ecdsa::signature::hazmat::PrehashVerifier;
564
565    #[test]
566    fn test_sign_p256() {
567        use p256::ecdsa::VerifyingKey;
568
569        let pk_u256: U256 = "1".parse().unwrap();
570        let signing_key = P256SigningKey::from_bytes(&pk_u256.to_be_bytes().into()).unwrap();
571        let digest = FixedBytes::from_hex(
572            "0x44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56",
573        )
574        .unwrap();
575
576        let result = sign_p256(&pk_u256, &digest).unwrap();
577        let result_bytes: [u8; 64] = result.try_into().unwrap();
578        let signature = P256Signature::from_bytes(&result_bytes.into()).unwrap();
579        let verifying_key = VerifyingKey::from(&signing_key);
580        assert!(verifying_key.verify_prehash(digest.as_slice(), &signature).is_ok());
581    }
582
583    #[test]
584    fn test_sign_p256_pk_too_large() {
585        // max n from https://neuromancer.sk/std/secg/secp256r1
586        let pk =
587            "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551".parse().unwrap();
588        let digest = FixedBytes::from_hex(
589            "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad",
590        )
591        .unwrap();
592        let result = sign_p256(&pk, &digest);
593        assert_eq!(
594            result.err().unwrap().to_string(),
595            "private key must be less than the NistP256 curve order (115792089210356248762697446949407573529996955224135760342422259061068512044369)"
596        );
597    }
598
599    #[test]
600    fn test_sign_p256_pk_0() {
601        let digest = FixedBytes::from_hex(
602            "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad",
603        )
604        .unwrap();
605        let result = sign_p256(&U256::ZERO, &digest);
606        assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0");
607    }
608
609    #[test]
610    fn test_sign_with_nonce_varies_and_recovers() {
611        // Given a fixed private key and digest
612        let pk_u256: U256 = U256::from(1u64);
613        let digest = FixedBytes::from_hex(
614            "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
615        )
616        .unwrap();
617
618        // Two distinct nonces
619        let n1: U256 = U256::from(123u64);
620        let n2: U256 = U256::from(456u64);
621
622        // Sign with both nonces
623        let sig1 = sign_with_nonce(&pk_u256, &digest, &n1).expect("sig1");
624        let sig2 = sign_with_nonce(&pk_u256, &digest, &n2).expect("sig2");
625
626        // (r,s) must differ when nonce differs
627        assert!(
628            sig1.r() != sig2.r() || sig1.s() != sig2.s(),
629            "signatures should differ with different nonces"
630        );
631
632        // ecrecover must yield the address for both signatures
633        let sk = parse_private_key(&pk_u256).unwrap();
634        let expected = alloy_signer::utils::secret_key_to_address(&sk);
635
636        assert_eq!(sig1.recover_address_from_prehash(&digest).unwrap(), expected);
637        assert_eq!(sig2.recover_address_from_prehash(&digest).unwrap(), expected);
638    }
639
640    #[test]
641    fn test_sign_with_nonce_zero_nonce_errors() {
642        // nonce = 0 should be rejected
643        let pk_u256: U256 = U256::from(1u64);
644        let digest = FixedBytes::from_hex(
645            "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
646        )
647        .unwrap();
648        let n0: U256 = U256::ZERO;
649
650        let err = sign_with_nonce(&pk_u256, &digest, &n0).unwrap_err();
651        let msg = err.to_string();
652        assert!(msg.contains("nonce cannot be 0"), "unexpected error: {msg}");
653    }
654
655    #[test]
656    fn test_sign_with_nonce_nonce_ge_order_errors() {
657        // nonce >= n should be rejected
658        use k256::Secp256k1;
659        // Curve order n as U256
660        let n_u256 = U256::from_be_slice(&Secp256k1::ORDER.to_be_byte_array());
661
662        let pk_u256: U256 = U256::from(1u64);
663        let digest = FixedBytes::from_hex(
664            "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
665        )
666        .unwrap();
667
668        // Try exactly n (>= n invalid)
669        let err = sign_with_nonce(&pk_u256, &digest, &n_u256).unwrap_err();
670        let msg = err.to_string();
671        assert!(msg.contains("invalid nonce scalar"), "unexpected error: {msg}");
672    }
673
674    #[test]
675    fn test_create_ed25519_key_determinism() {
676        let salt = B256::from([1u8; 32]);
677        let result1 = create_ed25519_key(&salt).unwrap();
678        let result2 = create_ed25519_key(&salt).unwrap();
679        assert_eq!(result1, result2, "same salt should produce same keys");
680    }
681
682    #[test]
683    fn test_create_ed25519_key_different_salts() {
684        let salt1 = B256::from([1u8; 32]);
685        let salt2 = B256::from([2u8; 32]);
686        let result1 = create_ed25519_key(&salt1).unwrap();
687        let result2 = create_ed25519_key(&salt2).unwrap();
688        assert_ne!(result1, result2, "different salts should produce different keys");
689    }
690
691    #[test]
692    fn test_public_key_ed25519_consistency() {
693        let salt = B256::from([42u8; 32]);
694        let create_result = create_ed25519_key(&salt).unwrap();
695        let (expected_public, private): (B256, B256) =
696            <(B256, B256)>::abi_decode(&create_result).unwrap();
697
698        let derived_public_result = public_key_ed25519(&private).unwrap();
699        let derived_public = B256::abi_decode(&derived_public_result).unwrap();
700
701        assert_eq!(expected_public, derived_public, "derived public key should match");
702    }
703
704    #[test]
705    fn test_sign_and_verify_ed25519_valid() {
706        let salt = B256::from([123u8; 32]);
707        let create_result = create_ed25519_key(&salt).unwrap();
708        let (public_key, private_key): (B256, B256) =
709            <(B256, B256)>::abi_decode(&create_result).unwrap();
710
711        let namespace = b"test.namespace";
712        let message = b"hello world";
713        let sig_result = sign_ed25519(namespace, message, &private_key).unwrap();
714        let sig_bytes: Vec<u8> = Vec::abi_decode(&sig_result).unwrap();
715
716        let verify_result = verify_ed25519(&sig_bytes, namespace, message, &public_key).unwrap();
717        let valid = bool::abi_decode(&verify_result).unwrap();
718
719        assert!(valid, "signature should be valid");
720    }
721
722    #[test]
723    fn test_verify_ed25519_invalid_signature() {
724        let salt = B256::from([123u8; 32]);
725        let create_result = create_ed25519_key(&salt).unwrap();
726        let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap();
727
728        let invalid_sig = [0u8; 64];
729        let namespace = b"test.namespace";
730        let message = b"hello world";
731
732        let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap();
733        let valid = bool::abi_decode(&verify_result).unwrap();
734
735        assert!(!valid, "invalid signature should not verify");
736    }
737
738    #[test]
739    fn test_verify_ed25519_namespace_separation() {
740        let salt = B256::from([123u8; 32]);
741        let create_result = create_ed25519_key(&salt).unwrap();
742        let (public_key, private_key): (B256, B256) =
743            <(B256, B256)>::abi_decode(&create_result).unwrap();
744
745        let namespace_a = b"namespace.a";
746        let message = b"message";
747        let sig_result = sign_ed25519(namespace_a, message, &private_key).unwrap();
748        let sig_bytes: Vec<u8> = Vec::abi_decode(&sig_result).unwrap();
749
750        let namespace_b = b"namespace.b";
751        let verify_result = verify_ed25519(&sig_bytes, namespace_b, message, &public_key).unwrap();
752        let valid = bool::abi_decode(&verify_result).unwrap();
753        assert!(!valid, "signature with namespace A should not verify with namespace B");
754
755        let verify_result = verify_ed25519(&sig_bytes, namespace_a, message, &public_key).unwrap();
756        let valid = bool::abi_decode(&verify_result).unwrap();
757        assert!(valid, "signature should verify with correct namespace");
758    }
759
760    #[test]
761    fn test_verify_ed25519_invalid_signature_length() {
762        let salt = B256::from([123u8; 32]);
763        let create_result = create_ed25519_key(&salt).unwrap();
764        let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap();
765
766        let invalid_sig = [0u8; 32];
767        let namespace = b"test";
768        let message = b"message";
769
770        let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap();
771        let valid = bool::abi_decode(&verify_result).unwrap();
772        assert!(!valid, "signature with wrong length should not verify");
773    }
774}