Docs · How it works▾
Overview

How it works

Winternitz signs with keys made of hash chains, uses each key exactly once, and lets the account rotate to the next key inside the same transaction.

1. Keys are hash chains#

A hash function turns any input into a fixed 32-byte output. It is easy to compute forwards and practically impossible to reverse. A hash chain repeats it: start with a random secret and hash it 15 times.

  • The start of the chain (position 0) is secret.
  • The end of the chain (position 15) is public.
  • Anyone can walk forwards along a chain. Nobody can walk backwards.
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
position 0, secretposition equal to the digit (6), revealed in the signatureposition 15, public key
Each step is one keccak256 hash, which is easy to compute forwards and impossible to reverse. To sign the digit 6 you reveal the value at position 6; the verifier hashes it 9 more times and checks it lands on the public key.

2. Signing is revealing a point on each chain#

To sign, the wallet first hashes the message into 32 bytes and splits it into 64 digits between 0 and 15. For each digit it reveals the chain value at that position. The verifier hashes each revealed value forward until it reaches position 15 and checks that the result matches the public key.

Three extra checksum digits are added, bringing the total to 67 chains. The checksum stops an attacker from hashing revealed values forward to forge a different message: making one digit larger always makes the checksum smaller, and making a checksum digit smaller would mean reversing a hash.

ParameterValue
Hash functionkeccak256
Winternitz parameter w16 (4 bits per digit)
Chains per key67 (64 message + 3 checksum)
Signature size67 × 32 bytes = 2,144 bytes
Hashes to verifyat most 67 × 15 = 1,005 (about half on average)

3. A key signs only once#

Revealing points on a chain gives something away. After two different signatures with the same key, an attacker may be able to combine them into a signature for a third message. So a Winternitz key must never sign twice.

Winternitz solves this by giving your account an endless sequence of keys, all derived from one 32-byte seed, and using a new one for every transaction.

#0

burned

signed tx 1

#1

burned

signed tx 2

#2

active

signs next tx

#3

queued

committed next

#4

queued

4. The account rotates automatically#

Your account contract stores just one value: the hash of the public key that must sign next. Every transaction carries the hash of the key that comes after it, and the signed message covers that next key too.

Your wallet reads the current key index from the chain

It never trusts local state for this, so two devices can't disagree about which key is next.

It signs the operation with key #n

The signed message is keccak256(userOpHash ‖ hash of key #n+1), so nobody can swap in a different next key.

The EntryPoint asks your account to validate

The account recomputes the public key from the signature and compares it with the stored hash.

The account moves on to key #n+1

If the signature is valid, the account replaces the stored hash with key #n+1's hash and emits a KeyRotated event. Key #n can never sign again.

Your address never changes. Only the key behind it does.

5. Replays are rejected#

A signature cannot be reused in any form:

AttemptResult
Re-submit an operation that already went throughRejected by the EntryPoint nonce (AA25)
Attach an old signature to a new operationRejected: the signed message no longer matches (AA24)
Sign with a key that was already usedRejected: the account has moved on to the next key (AA24)
Change even one bit of a signatureRejected (fuzz-tested over all 2,176 bytes)

6. A recovery key unsticks stuck transactions#

If a transaction can never confirm (say, fees spiked), your key can't simply sign it again. Your seed also derives a separate chain of recovery keys. A recovery key signs one replacement with the same nonce and higher fees; the account then skips the stuck key and moves on to the next recovery key, emitting KeyRecovered. Main and recovery signatures have different formats, so neither can pass for the other.

7. Your first transaction deploys the account#

Your address is computed in advance with CREATE2, from the factory and your first main and recovery key hashes. You can receive funds before anything is deployed. The first transaction you send deploys the account contract and burns key #0 in the same step.

Want the exact specification?

The byte-level scheme (chain function, key derivation, digit encoding) is in Developers, and in full in docs/design.md in the repository.