.claude/skills/forge-signing/SKILL.md
Sign messages with Foundry cheatcodes for testing signature verification. Use when testing EIP-712 signatures, Permit2, or any signed data verification.
npx skillsauth add cyotee/crane forge-signingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Sign messages for testing signature verification in Solidity contracts.
import {Vm} from "forge-std/Vm.sol";
Vm constant vm = Vm(VM_ADDRESS);
Sign a digest with a private key:
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
Returns:
v - Recovery idr - First 32 bytes of signatures - Second 32 bytes of signatureUsing a Wallet struct:
Wallet memory wallet = vm.createWallet("alice");
(uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet, digest);
(address alice,) = makeAddrAndKey("alice");
bytes32 hash = keccak256("Signed by Alice");
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, hash);
address signer = ecrecover(hash, v, r, s);
assertEq(alice, signer); // PASS
function testEIP712Signature() public {
(address alice, uint256 alicePk) = makeAddrAndKey("alice");
// Build EIP-712 domain separator
bytes32 domainSeparator = keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("MyContract"),
keccak256("1"),
block.chainid,
address(this)
));
// Build typed data hash
bytes32 structHash = keccak256(abi.encode(
keccak256("Message(address sender,uint256 amount)"),
alice,
100e18
));
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
domainSeparator,
structHash
));
// Sign
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, digest);
bytes memory signature = abi.encodePacked(r, s, v);
// Verify in contract
assertTrue(verifySignature(alice, digest, signature));
}
function testPermit2Signature() public {
(address alice, uint256 alicePk) = makeAddrAndKey("alice");
// Build permit data (matching Permit2's expected format)
bytes32 digest = // ... build according to Permit2 spec
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, digest);
bytes memory signature = abi.encodePacked(r, s, v);
// Call contract that verifies Permit2 signature
router.swapSingleTokenExactInWithPermit(..., signature);
}
When encoding for OpenZeppelin's ECDSA library:
// WRONG:
bytes memory signature = abi.encodePacked(v, r, s);
// CORRECT:
bytes memory signature = abi.encodePacked(r, s, v);
OpenZeppelin expects (r, s, v) order, not (v, r, s).
| Cheatcode | Purpose |
|-----------|---------|
| vm.addr(uint256) | Get address from private key |
| vm.createWallet(string) | Create Wallet struct |
| vm.createWallet(string, uint256) | Create wallet with specific key |
| vm.sign(Wallet, bytes32) | Sign with Wallet struct |
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
documentation
Write to contracts and send transactions. Use when executing state-changing contract functions.
development
HTTP and WebSocket transports for blockchain connectivity. Use when configuring network connections.
data-ai
Read contract data with type-safe ABI. Use when querying smart contract view/pure functions.