Skip to content

Add vm.signWithNonce(privateKey, digest, nonce) cheatcode (Crypto) #11266

@Ectario

Description

@Ectario

Component

Forge

Describe the feature you would like

Feature request: vm.signWithNonce(privateKey, digest, nonce) cheatcode

Summary

Add a new Foundry cheatcode for signing a 32-byte digest on secp256k1 with a user-supplied nonce k.
This bypasses RFC6979 deterministic nonce derivation and allows precise control of k for testing purposes.

(v, r, s) = vm.signWithNonce(uint256 privateKey, bytes32 digest, uint256 nonce);
  • privateKey: secp256k1 private key (0 < pk < n)
  • digest: 32-byte prehash (as in vm.sign)
  • nonce: ephemeral scalar k (0 < k < n)

Return values are (v, r, s) with v ∈ {27, 28} for compatibility with ecrecover.


Motivation / Problem it solves

Currently, vm.sign derives k deterministically (RFC6979), which is safe for normal signing but does not allow controlling k in tests.
Security researchers and test writers often need to:

  • Test systems relying on signature uniqueness by intentionally producing multiple valid signatures for the same message or key.
  • Perform fuzzing and stress testing of signature verification code under controlled, reproducible conditions with chosen k.

This cheatcode enables those scenarios.


Safety Notes

  • WARNING: Using the same nonce k with the same private key for two different messages will leak the private key.
  • nonce must be generated with a cryptographically secure RNG.
  • This feature is intended primarily for security testing, not production signing.

Implementation sketch

  • Implemented in cheatcodes/src/crypto.rs using k256 hazmat ECDSA APIs.
  • Strict scalar construction (Scalar::from_repr(...).into_option()), rejects 0 and ≥ n.
  • Low-s normalization applied to match Ethereum consensus rules.
  • Recovery id from hazmat when available, otherwise determined by trial recovery against the expected address.
  • Solidity tests in testdata/default/cheats/test/Sign.t.sol.

Example usage

function testSignWithNonce() public {
    uint256 pk = 1234;
    bytes32 digest = keccak256("hello");
    uint256 nonce = 42;

    (uint8 v, bytes32 r, bytes32 s) = vm.signWithNonce(pk, digest, nonce);
    assertEq(ecrecover(digest, v, r, s), vm.addr(pk));
}

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions