Skip to content

feat(node): accept a wallet's personal_sign signature on a transaction - #23

Merged
MehranMazhar merged 1 commit into
mainfrom
feat/wallet-signatures-eip191
Oct 6, 2026
Merged

MehranMazhar merged 1 commit into
mainfrom
feat/wallet-signatures-eip191

Conversation

@MehranMazhar

Copy link
Copy Markdown
Member

What this changes

The node now accepts a wallet's personal_sign signature on a transaction, next to the signature it accepted before.

MetaMask and Trust Wallet will not sign a bare hash. They sign a text, and they hash it with a fixed prefix (EIP-191). Until now the node checked only a signature over the hash string itself, so no wallet could send a transaction.

Transaction::verify_signature tries two schemes. Each one is checked against its own digest, so one cannot pass for the other:

Scheme The key signs Used by
1 (unchanged) the hash string the SDK's local key, the mint authority, the faucet
2 (new) clutch-tx:{chain_id}:{hash} with personal_sign a wallet

In scheme 2 the hash is 64 lowercase hex characters with no 0x. The text names the chain, and verify_hash already ties the hash to chain_id, so a signature cannot move to another chain or to another transaction.

The wire format, the hash, the block signature and the mint cosignatures do not change.

This is a consensus change

A node without this change rejects a block that carries a scheme 2 transaction. Every validator must run this build before the first wallet transaction is sent. Plan: stage gets it first (the 3 stage nodes move together); mainnet gets it through a chain reset, because no CLT has ever been minted there.

Tests

  • personal_sign_bytes_has_the_eip191_layout
  • personal_sign_digest_matches_the_published_hello_world_vector (the digest comes from the ethers documentation, not from this code)
  • a_signature_made_by_a_javascript_library_verifies (made by @noble/secp256k1, which the SDK uses)
  • a_wallet_signature_binds_the_signer_and_the_exact_text
  • in transaction.rs: wallet_signing_text_names_the_chain_and_the_bare_hash, verify_signature_accepts_a_wallet_signature, verify_signature_accepts_the_sign_personal_helper, verify_signature_still_accepts_a_signature_over_the_hash_string, verify_signature_accepts_the_signature_a_javascript_library_made, and five refusals (another chain, another key, another transaction, a flipped recovery id, a malformed signature)
  • tests/wallet_signature.rs: a wallet-signed transfer goes through the pool and an authored block and is applied; two bad signatures are refused

Nothing was run on the author's machine. The Test workflow is the check.

Also

CLAUDE.md says the new scheme in "Gotchas", and its old line "There is no CI job running cargo test" is corrected: test.yml has run on every PR for some time.

🤖 Generated with Claude Code

MetaMask and Trust Wallet will not sign a bare hash. They sign a text with
personal_sign (EIP-191), which hashes "\x19Ethereum Signed Message:\n" + length
+ text. Until now the node only accepted a signature over the hash string
itself, so no wallet could send a transaction.

Transaction::verify_signature now tries two schemes, each against its own
digest:

1. the key signed the hash string (the SDK's local key, the mint authority,
   the faucet). Unchanged.
2. a wallet signed "clutch-tx:{chain_id}:{hash}" with personal_sign, with the
   hash as 64 lowercase hex and no 0x. The text names the chain, and
   verify_hash already ties the hash to chain_id.

The wire format, the hash, the block signature and the mint cosignatures do not
change. A malformed signature keeps its old error text.

This is a consensus change: a node without it rejects a block that carries a
scheme 2 transaction. Every validator must run this build before the first
wallet transaction is sent.

SignatureKeys::personal_sign_bytes builds the prefixed bytes. sign,
recover_address and verify already hash what they are given with Keccak-256, so
they serve both schemes with no new code path.

Tests: the layout of the prefixed bytes; the published ethers digest for
"Hello World"; a signature made by @noble/secp256k1 (what the SDK uses) that
the node verifies; sign and verify unit cases for the wrong chain, the wrong key,
another transaction, a flipped recovery id and a malformed signature; and
tests/wallet_signature.rs, which sends a wallet-signed transfer through the pool
and an authored block, and refuses two bad ones.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@MehranMazhar
MehranMazhar merged commit 95ea975 into main Oct 6, 2026
5 checks passed
@MehranMazhar
MehranMazhar deleted the feat/wallet-signatures-eip191 branch October 6, 2026 05:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant