From a6b0410f771e9cef6e2eb96ff39bd573d89b2f48 Mon Sep 17 00:00:00 2001 From: Ectario Date: Sun, 10 Aug 2025 00:04:17 +0200 Subject: [PATCH 1/9] feat: adding sign_wih_nonce in cheatcodes crates --- crates/cheatcodes/spec/src/vm.rs | 6 ++ crates/cheatcodes/src/crypto.rs | 153 ++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 2 deletions(-) diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index c3722aae73ab7..d2703a05f2ef2 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -2672,6 +2672,12 @@ interface Vm { #[cheatcode(group = Crypto)] function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + /// Signs `digest` with `privateKey` on the secp256k1 curve, using the given `nonce` + /// as the raw ephemeral k value in ECDSA (instead of deriving it deterministically). + #[cheatcode(group = Crypto)] + function signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); + + /// Signs `digest` with `privateKey` using the secp256k1 curve. /// /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index 0b1f9ffdd07a6..510dbe239c69d 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -12,9 +12,9 @@ use alloy_signer_local::{ }; use alloy_sol_types::SolValue; use k256::{ - ecdsa::SigningKey, - elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint}, + ecdsa::{hazmat, SigningKey}, elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint}, FieldBytes, Scalar }; + use p256::ecdsa::{ Signature as P256Signature, SigningKey as P256SigningKey, signature::hazmat::PrehashSigner, }; @@ -51,6 +51,16 @@ impl Cheatcode for sign_0Call { } } +impl Cheatcode for signWithNonceCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let pk: U256 = self.privateKey; + let digest: B256 = self.digest; + let nonce: U256 = self.nonce; + let sig: alloy_primitives::Signature = sign_with_nonce(&pk, &digest, &nonce)?; + Ok(encode_full_sig(sig)) + } +} + impl Cheatcode for signCompact_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { wallet, digest } = self; @@ -241,6 +251,83 @@ fn sign(private_key: &U256, digest: &B256) -> Result Result { + let d_scalar: Scalar = + ::from_repr(private_key.to_be_bytes().into()) + .into_option() + .ok_or_else(|| fmt_err!("invalid private key scalar"))?; + if bool::from(d_scalar.is_zero()) { + return Err(fmt_err!("private key cannot be 0")); + } + + let k_scalar: Scalar = + ::from_repr(nonce.to_be_bytes().into()) + .into_option() + .ok_or_else(|| fmt_err!("invalid nonce scalar"))?; + if bool::from(k_scalar.is_zero()) { + return Err(fmt_err!("nonce cannot be 0")); + } + + let mut z = [0u8; 32]; + z.copy_from_slice(digest.as_slice()); + let z_fb: FieldBytes = FieldBytes::from(z); + + // Hazmat signing using the scalar `d` (SignPrimitive is implemented for `Scalar`) + // Note: returns (Signature, Option) + let (sig_raw, recid_opt) = + > + ::try_sign_prehashed(&d_scalar, k_scalar, &z_fb) + .map_err(|e| fmt_err!("sign_prehashed failed: {e}"))?; + + // Enforce low-s; if mirrored, parity flips (we’ll account for it below if we use recid) + let (sig_low, flipped) = if let Some(norm) = sig_raw.normalize_s() { + (norm, true) + } else { + (sig_raw, false) + }; + + let r_u256 = U256::from_be_bytes(sig_low.r().to_bytes().into()); + let s_u256 = U256::from_be_bytes(sig_low.s().to_bytes().into()); + + // Determine v parity in {0,1} + let v_parity = if let Some(id) = recid_opt { + let mut v = id.to_byte() & 1; + if flipped { v ^= 1; } + v + } else { + // Fallback: choose parity by recovery to expected address + let expected_addr = { + let sk: SigningKey = parse_private_key(private_key)?; + alloy_signer::utils::secret_key_to_address(&sk) + }; + // Try v = 0 + let cand0 = alloy_primitives::Signature::new(r_u256, s_u256, false); + if cand0.recover_address_from_prehash(digest).ok() == Some(expected_addr) { + return Ok(cand0); + } + // Try v = 1 + let cand1 = alloy_primitives::Signature::new(r_u256, s_u256, true); + if cand1.recover_address_from_prehash(digest).ok() == Some(expected_addr) { + return Ok(cand1); + } + return Err(fmt_err!("failed to determine recovery id for signature")); + }; + + let y_parity = v_parity != 0; + Ok(alloy_primitives::Signature::new(r_u256, s_u256, y_parity)) +} + + fn sign_with_wallet( state: &mut Cheatcodes, signer: Option
, @@ -393,6 +480,7 @@ fn derive_wallets( mod tests { use super::*; use alloy_primitives::{FixedBytes, hex::FromHex}; + use k256::elliptic_curve::Curve; use p256::ecdsa::signature::hazmat::PrehashVerifier; #[test] @@ -438,4 +526,65 @@ mod tests { let result = sign_p256(&U256::ZERO, &digest); assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0"); } + + #[test] + fn test_sign_with_nonce_varies_and_recovers() { + // Given a fixed private key and digest + let pk_u256: U256 = U256::from(1u64); + let digest = FixedBytes::from_hex( + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ).unwrap(); + + // Two distinct nonces + let n1: U256 = U256::from(123u64); + let n2: U256 = U256::from(456u64); + + // Sign with both nonces + let sig1 = sign_with_nonce(&pk_u256, &digest, &n1).expect("sig1"); + let sig2 = sign_with_nonce(&pk_u256, &digest, &n2).expect("sig2"); + + // (r,s) must differ when nonce differs + assert!(sig1.r() != sig2.r() || sig1.s() != sig2.s(), "signatures should differ with different nonces"); + + // ecrecover must yield the address for both signatures + let sk = parse_private_key(&pk_u256).unwrap(); + let expected = alloy_signer::utils::secret_key_to_address(&sk); + + assert_eq!(sig1.recover_address_from_prehash(&digest).unwrap(), expected); + assert_eq!(sig2.recover_address_from_prehash(&digest).unwrap(), expected); + } + + #[test] + fn test_sign_with_nonce_zero_nonce_errors() { + // nonce = 0 should be rejected + let pk_u256: U256 = U256::from(1u64); + let digest = FixedBytes::from_hex( + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + ).unwrap(); + let n0: U256 = U256::ZERO; + + let err = sign_with_nonce(&pk_u256, &digest, &n0).unwrap_err(); + let msg = err.to_string(); + // Accept either of the explicit messages (depending on which branch triggers first) + assert!(msg.contains("nonce cannot be 0") || msg.contains("invalid nonce scalar"), + "unexpected error: {msg}"); + } + + #[test] + fn test_sign_with_nonce_nonce_ge_order_errors() { + // nonce >= n should be rejected + use k256::Secp256k1; + // Curve order n as U256 + let n_u256 = U256::from_be_slice(&Secp256k1::ORDER.to_be_byte_array()); + + let pk_u256: U256 = U256::from(1u64); + let digest = FixedBytes::from_hex( + "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ).unwrap(); + + // Try exactly n (>= n invalid) + let err = sign_with_nonce(&pk_u256, &digest, &n_u256).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("invalid nonce scalar"), "unexpected error: {msg}"); + } } From 5f3cc586ec35eba90cec1e7469d3a5a8861c2fdb Mon Sep 17 00:00:00 2001 From: Ectario Date: Sun, 10 Aug 2025 11:27:32 +0200 Subject: [PATCH 2/9] test: adding sign with nonce solidity unit tests --- crates/cheatcodes/src/crypto.rs | 5 ++-- testdata/cheats/Vm.sol | 1 + testdata/default/cheats/Sign.t.sol | 40 ++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index 510dbe239c69d..99d073914d71a 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -527,7 +527,7 @@ mod tests { assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0"); } - #[test] + #[test] fn test_sign_with_nonce_varies_and_recovers() { // Given a fixed private key and digest let pk_u256: U256 = U256::from(1u64); @@ -565,8 +565,7 @@ mod tests { let err = sign_with_nonce(&pk_u256, &digest, &n0).unwrap_err(); let msg = err.to_string(); - // Accept either of the explicit messages (depending on which branch triggers first) - assert!(msg.contains("nonce cannot be 0") || msg.contains("invalid nonce scalar"), + assert!(msg.contains("nonce cannot be 0"), "unexpected error: {msg}"); } diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 5959572375115..1c26b255a0253 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -495,6 +495,7 @@ interface Vm { function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + function signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function skip(bool skipTest, string calldata reason) external; function sleep(uint256 duration) external; diff --git a/testdata/default/cheats/Sign.t.sol b/testdata/default/cheats/Sign.t.sol index 937ebc00a9222..4d21ec4d96e63 100644 --- a/testdata/default/cheats/Sign.t.sol +++ b/testdata/default/cheats/Sign.t.sol @@ -44,4 +44,44 @@ contract SignTest is DSTest { function testSignCompactMessage(uint248 pk, bytes memory message) public { testSignCompactDigest(pk, keccak256(message)); } + + /// secp256k1 subgroup order n + function _secp256k1Order() internal pure returns (uint256) { + return 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; + } + + function testSignWithNonceDigestDifferentNonces(uint248 pk, bytes32 digest) public { + vm.assume(pk != 0); + uint256 n1 = 123; + uint256 n2 = 456; + vm.assume(n1 != 0 && n2 != 0 && n1 != n2); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.signWithNonce(pk, digest, n1); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.signWithNonce(pk, digest, n2); + assertTrue(r1 != r2 || s1 != s2, "signatures should differ for different nonces"); + address expected = vm.addr(pk); + assertEq(ecrecover(digest, v1, r1, s1), expected, "recover for nonce n1 failed"); + assertEq(ecrecover(digest, v2, r2, s2), expected, "recover for nonce n2 failed"); + } + + function testSignWithNonceDigestSameNonceDeterministic(uint248 pk, bytes32 digest) public { + vm.assume(pk != 0); + uint256 n = 777; + vm.assume(n != 0); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.signWithNonce(pk, digest, n); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.signWithNonce(pk, digest, n); + assertEq(v1, v2, "v should match"); + assertEq(r1, r2, "r should match"); + assertEq(s1, s2, "s should match"); + address expected = vm.addr(pk); + assertEq(ecrecover(digest, v1, r1, s1), expected, "recover failed"); + } + + function testSignWithNonceInvalidNoncesRevert(uint248 pk, bytes32 digest) public { + vm.assume(pk != 0); + vm.expectRevert(); + vm.signWithNonce(pk, digest, 0); + uint256 n = _secp256k1Order(); + vm.expectRevert(); + vm.signWithNonce(pk, digest, n); + } } From ee0c5c017690ec92bb1d961fbf074aa25440e6c3 Mon Sep 17 00:00:00 2001 From: Ectario Date: Sun, 10 Aug 2025 12:25:50 +0200 Subject: [PATCH 3/9] style: formatting --- crates/cheatcodes/src/crypto.rs | 49 +++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index 99d073914d71a..8c73f9b683e8a 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -12,7 +12,9 @@ use alloy_signer_local::{ }; use alloy_sol_types::SolValue; use k256::{ - ecdsa::{hazmat, SigningKey}, elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint}, FieldBytes, Scalar + FieldBytes, Scalar, + ecdsa::{SigningKey, hazmat}, + elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint}, }; use p256::ecdsa::{ @@ -256,7 +258,8 @@ fn sign(private_key: &U256, digest: &B256) -> Result) let (sig_raw, recid_opt) = - > - ::try_sign_prehashed(&d_scalar, k_scalar, &z_fb) - .map_err(|e| fmt_err!("sign_prehashed failed: {e}"))?; + >::try_sign_prehashed( + &d_scalar, k_scalar, &z_fb, + ) + .map_err(|e| fmt_err!("sign_prehashed failed: {e}"))?; // Enforce low-s; if mirrored, parity flips (we’ll account for it below if we use recid) - let (sig_low, flipped) = if let Some(norm) = sig_raw.normalize_s() { - (norm, true) - } else { - (sig_raw, false) - }; + let (sig_low, flipped) = + if let Some(norm) = sig_raw.normalize_s() { (norm, true) } else { (sig_raw, false) }; let r_u256 = U256::from_be_bytes(sig_low.r().to_bytes().into()); let s_u256 = U256::from_be_bytes(sig_low.s().to_bytes().into()); @@ -302,7 +303,9 @@ fn sign_with_nonce( // Determine v parity in {0,1} let v_parity = if let Some(id) = recid_opt { let mut v = id.to_byte() & 1; - if flipped { v ^= 1; } + if flipped { + v ^= 1; + } v } else { // Fallback: choose parity by recovery to expected address @@ -327,7 +330,6 @@ fn sign_with_nonce( Ok(alloy_primitives::Signature::new(r_u256, s_u256, y_parity)) } - fn sign_with_wallet( state: &mut Cheatcodes, signer: Option
, @@ -532,8 +534,9 @@ mod tests { // Given a fixed private key and digest let pk_u256: U256 = U256::from(1u64); let digest = FixedBytes::from_hex( - "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - ).unwrap(); + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ) + .unwrap(); // Two distinct nonces let n1: U256 = U256::from(123u64); @@ -544,7 +547,10 @@ mod tests { let sig2 = sign_with_nonce(&pk_u256, &digest, &n2).expect("sig2"); // (r,s) must differ when nonce differs - assert!(sig1.r() != sig2.r() || sig1.s() != sig2.s(), "signatures should differ with different nonces"); + assert!( + sig1.r() != sig2.r() || sig1.s() != sig2.s(), + "signatures should differ with different nonces" + ); // ecrecover must yield the address for both signatures let sk = parse_private_key(&pk_u256).unwrap(); @@ -559,14 +565,14 @@ mod tests { // nonce = 0 should be rejected let pk_u256: U256 = U256::from(1u64); let digest = FixedBytes::from_hex( - "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" - ).unwrap(); + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + ) + .unwrap(); let n0: U256 = U256::ZERO; let err = sign_with_nonce(&pk_u256, &digest, &n0).unwrap_err(); let msg = err.to_string(); - assert!(msg.contains("nonce cannot be 0"), - "unexpected error: {msg}"); + assert!(msg.contains("nonce cannot be 0"), "unexpected error: {msg}"); } #[test] @@ -578,8 +584,9 @@ mod tests { let pk_u256: U256 = U256::from(1u64); let digest = FixedBytes::from_hex( - "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - ).unwrap(); + "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + ) + .unwrap(); // Try exactly n (>= n invalid) let err = sign_with_nonce(&pk_u256, &digest, &n_u256).unwrap_err(); From beda0dfab7d644be0c1c3eb8a4499f78571ec909 Mon Sep 17 00:00:00 2001 From: Ectario Date: Sun, 10 Aug 2025 15:19:26 +0200 Subject: [PATCH 4/9] test: fixing solidity invalid nonce test --- testdata/default/cheats/Sign.t.sol | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/testdata/default/cheats/Sign.t.sol b/testdata/default/cheats/Sign.t.sol index 4d21ec4d96e63..98d457e1e219e 100644 --- a/testdata/default/cheats/Sign.t.sol +++ b/testdata/default/cheats/Sign.t.sol @@ -76,12 +76,30 @@ contract SignTest is DSTest { assertEq(ecrecover(digest, v1, r1, s1), expected, "recover failed"); } - function testSignWithNonceInvalidNoncesRevert(uint248 pk, bytes32 digest) public { - vm.assume(pk != 0); - vm.expectRevert(); - vm.signWithNonce(pk, digest, 0); + function testSignWithNonceInvalidNoncesRevert() public { + uint256 pk = 1; + bytes32 digest = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; + + (bool ok, bytes memory data) = HEVM_ADDRESS.call(abi.encodeWithSelector(Vm.signWithNonce.selector, pk, digest, 0)); + assertTrue(!ok, "expected revert on nonce=0"); + assertEq(_revertString(data), "vm.signWithNonce: nonce cannot be 0"); + uint256 n = _secp256k1Order(); - vm.expectRevert(); - vm.signWithNonce(pk, digest, n); + (ok, data) = HEVM_ADDRESS.call(abi.encodeWithSelector(Vm.signWithNonce.selector, pk, digest, n)); + assertTrue(!ok, "expected revert on nonce >= n"); + assertEq(_revertString(data), "vm.signWithNonce: invalid nonce scalar"); } + + /// Decode revert payload + /// by stripping the 4-byte selector and ABI-decoding the tail as `string`. + function _revertString(bytes memory data) internal pure returns (string memory) { + if (data.length < 4) return ""; + // copy data[4:] into a new bytes + bytes memory tail = new bytes(data.length - 4); + for (uint256 i = 0; i < tail.length; i++) { + tail[i] = data[i + 4]; + } + return abi.decode(tail, (string)); + } + } From 6efc47d0c8efa5c5d2576a3dfacb2166a4d41a7a Mon Sep 17 00:00:00 2001 From: Ectario Date: Sun, 10 Aug 2025 16:37:23 +0200 Subject: [PATCH 5/9] style: fix formatting in Sign.t.sol --- testdata/default/cheats/Sign.t.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/testdata/default/cheats/Sign.t.sol b/testdata/default/cheats/Sign.t.sol index 98d457e1e219e..899a203f8c323 100644 --- a/testdata/default/cheats/Sign.t.sol +++ b/testdata/default/cheats/Sign.t.sol @@ -79,11 +79,10 @@ contract SignTest is DSTest { function testSignWithNonceInvalidNoncesRevert() public { uint256 pk = 1; bytes32 digest = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; - - (bool ok, bytes memory data) = HEVM_ADDRESS.call(abi.encodeWithSelector(Vm.signWithNonce.selector, pk, digest, 0)); + (bool ok, bytes memory data) = + HEVM_ADDRESS.call(abi.encodeWithSelector(Vm.signWithNonce.selector, pk, digest, 0)); assertTrue(!ok, "expected revert on nonce=0"); assertEq(_revertString(data), "vm.signWithNonce: nonce cannot be 0"); - uint256 n = _secp256k1Order(); (ok, data) = HEVM_ADDRESS.call(abi.encodeWithSelector(Vm.signWithNonce.selector, pk, digest, n)); assertTrue(!ok, "expected revert on nonce >= n"); @@ -101,5 +100,4 @@ contract SignTest is DSTest { } return abi.decode(tail, (string)); } - } From 0a9d93653604ea22d976b6f75067fbb5d6c4e845 Mon Sep 17 00:00:00 2001 From: Ectario Date: Tue, 12 Aug 2025 15:14:48 +0200 Subject: [PATCH 6/9] ci: fix cheatcode specs test problem --- crates/cheatcodes/assets/cheatcodes.json | 20 ++++++++++++++++++++ testdata/cheats/Vm.sol | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index f2987421ca6ec..285f38883942a 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -10370,6 +10370,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "signWithNonce", + "description": "Signs `digest` with `privateKey` on the secp256k1 curve, using the given `nonce`\nas the raw ephemeral k value in ECDSA (instead of deriving it deterministically).", + "declaration": "function signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "signWithNonce(uint256,bytes32,uint256)", + "selector": "0xe11cb8fc", + "selectorBytes": [ + 225, + 28, + 184, + 252 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "sign_0", diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index a800ec761ba09..5ea54f61fb52b 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -511,11 +511,11 @@ interface Vm { function signDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation); function signDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation); function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); + function signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); - function signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function skip(bool skipTest, string calldata reason) external; function sleep(uint256 duration) external; From 269d8517382c27c354ef93080cfb171f14e56382 Mon Sep 17 00:00:00 2001 From: Ectario <61197119+Ectario@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:06:20 +0200 Subject: [PATCH 7/9] Update crates/cheatcodes/spec/src/vm.rs review modification Co-authored-by: onbjerg --- crates/cheatcodes/spec/src/vm.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 73c8960c825c2..fe750d6fdcd1a 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -2860,7 +2860,6 @@ interface Vm { #[cheatcode(group = Crypto)] function signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); - /// Signs `digest` with `privateKey` using the secp256k1 curve. /// /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the From 1b24256511a5433369add7eab88645624a9c37d8 Mon Sep 17 00:00:00 2001 From: Ectario <61197119+Ectario@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:06:53 +0200 Subject: [PATCH 8/9] Update crates/cheatcodes/src/crypto.rs review modification (better format) Co-authored-by: onbjerg --- crates/cheatcodes/src/crypto.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index 8c73f9b683e8a..81740b6237a41 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -253,13 +253,16 @@ fn sign(private_key: &U256, digest: &B256) -> Result Date: Thu, 21 Aug 2025 19:26:19 +0200 Subject: [PATCH 9/9] refactor: changing cheatcode name (append Unsafe to it) --- crates/cheatcodes/assets/cheatcodes.json | 16 ++++++++-------- crates/cheatcodes/spec/src/vm.rs | 2 +- crates/cheatcodes/src/crypto.rs | 6 +++--- testdata/cheats/Vm.sol | 2 +- testdata/default/cheats/Sign.t.sol | 22 +++++++++++----------- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 5892a60b441eb..3cfc27fc61038 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -10672,18 +10672,18 @@ }, { "func": { - "id": "signWithNonce", + "id": "signWithNonceUnsafe", "description": "Signs `digest` with `privateKey` on the secp256k1 curve, using the given `nonce`\nas the raw ephemeral k value in ECDSA (instead of deriving it deterministically).", - "declaration": "function signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s);", + "declaration": "function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", "mutability": "pure", - "signature": "signWithNonce(uint256,bytes32,uint256)", - "selector": "0xe11cb8fc", + "signature": "signWithNonceUnsafe(uint256,bytes32,uint256)", + "selector": "0x2012783a", "selectorBytes": [ - 225, - 28, - 184, - 252 + 32, + 18, + 120, + 58 ] }, "group": "crypto", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index fe750d6fdcd1a..8a5a7859ff32b 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -2858,7 +2858,7 @@ interface Vm { /// Signs `digest` with `privateKey` on the secp256k1 curve, using the given `nonce` /// as the raw ephemeral k value in ECDSA (instead of deriving it deterministically). #[cheatcode(group = Crypto)] - function signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); + function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); /// Signs `digest` with `privateKey` using the secp256k1 curve. /// diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index 81740b6237a41..924a44ef7ce32 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -53,7 +53,7 @@ impl Cheatcode for sign_0Call { } } -impl Cheatcode for signWithNonceCall { +impl Cheatcode for signWithNonceUnsafeCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let pk: U256 = self.privateKey; let digest: B256 = self.digest; @@ -256,8 +256,8 @@ fn sign(private_key: &U256, digest: &B256) -> Result= n"); - assertEq(_revertString(data), "vm.signWithNonce: invalid nonce scalar"); + assertEq(_revertString(data), "vm.signWithNonceUnsafe: invalid nonce scalar"); } /// Decode revert payload