Skip to content

FORS+C Parameters

The current parameter set

Current selection

The active set is defined at the top of src/Verifiers/ForsVerifier.sol:

uint256 constant FORS_N = 16; // hash truncation / node size (bytes)
uint256 constant FORS_K = 26; // FORS trees (only K-1 = 25 are real under +C)
uint256 constant FORS_A = 5;  // tree height -> 2^5 = 32 leaves per tree
PropertyValue
Hash primitivetruncated Keccak-256 (N = 16 bytes)
Trees K26 (25 real, 1 dropped by +C)
Tree height A5 (32 leaves per tree)
Signature size2,448 bytes
Onchain public key20-byte address
Verifier gas~34.1k warm / ~38.1k cold (measured)
Signer work~2,400 Keccak calls / signature
q=1 security128-bit classical (NIST Level 1)
Domain byte0xFD

Signature layout

A FORS+C signature is exactly FORS_SIG_LEN = 2,448 bytes:

offset   length   field
0        16       R           (per-signature randomizer)
16       16       pkSeed      (public per-keypair seed)
32       2400     tree openings for t = 0..24   (25 × 96 B)
2432     16       counter     (+C grinding nonce, big-endian uint128)

Each of the 25 tree openings is 96 bytes:

offset within tree   length   field
0                    16       sk for the selected leaf
16                   16       auth[0]   (leaf-level sibling)
32                   16       auth[1]
48                   16       auth[2]
64                   16       auth[3]
80                   16       auth[4]   (root-level sibling)

Derived constants (all recomputed from K, A, N):

TREE_LEN     = 16 + A·16        = 96
SECTION_LEN  = (K-1) · TREE_LEN = 2,400
SIG_LEN      = 16 + 16 + 2,400 + 16 = 2,448

The K-th tree's auth path is omitted entirely: the verifier knows its index is 0 from the grinding constraint and never opens it.

Security and key reuse

After q signatures under one key, each tree has up to q revealed leaves out of 2^A. Forging requires a message whose K hash-derived indices all land on revealed leaves, so the work is roughly:

security(q) ≈ min( n , K · (A − log₂ q) )   bits, classical

capped at n = 128 (the hash truncation). For K = 26, A = 5:

q (signatures per key)Classical security
1128 bits (NIST Level 1, capped)
2104 bits
389 bits
478 bits
570 bits

q = 1 sits at the 128-bit ceiling (K · A = 130 just clears the cap). Degradation is steep: reuse beyond q = 2 drops below Level 1. That is acceptable because the design target is one signature per key, enforced by atomic rotation.

Recommended reuse budget

The few-time property exists to make failure modes (dropped UserOp, bundler replacement, a burn-before-sign desync) recoverable rather than catastrophic. Wallets MUST track per-key reuse and enforce a bounded budget:

  • q ≤ 2: normal-operation envelope (≥ 104 bits). Replacement-transaction flows fall here.
  • q ≤ 5: maximum under degraded security (≥ 70 bits) for emergency reuse.
  • q > 5: the wallet MUST refuse to sign with that key.

This is the central reason FORS+C replaced WOTS+C: under WOTS+C, the same failure modes leak enough chain information for immediate classical forgery.

The two-forest cache

To keep signing interactive, the wallet keeps two full FORS forests ready at all times, where a "forest" means all signer material for one keypair (skSeed, pkSeed, cached tree nodes, pkRoot, owner address):

current: forest that authorizes the next UserOp
next:    forest whose address is appended as nextOwner

After releasing a signature it starts building next + 1 in the background, so the user never waits at a confirmation screen. The invariant is that address(S_{i+1}) must be known before signing UserOp i, because nextOwner is part of the signed userOpHash; the full forest for S_{i+1} only needs to be ready by UserOp i+1.

Memory (public tree nodes, N = 16), under +C with one tree omitted:

SetPer forestTwo forests
K=26, A=5~25 KB~50 KB

Leaf secrets are derived on demand from skSeed rather than cached, to avoid persisting more secret material than necessary. The cache is a UX optimization only: the contract cares solely that the current owner verifies the UserOp and that the appended nextOwner is nonzero. See docs/fors-two-forest-cache.md for the full state machine.

Hardware-wallet feasibility

The K=26, A=5 set was chosen specifically to keep signing viable on constrained devices: dropping from A=10 to A=5 (1,024 → 32 leaves per tree) cuts signer work by roughly 17×, to ~2,400 Keccak calls per signature, paid for by steeper q-degradation that the rotation-per-UserOp model can absorb.

On a phone or desktop CPU this workload is negligible. On secure-element hardware with software Keccak it is noticeably slower but still workable; if interactive secure-element UX becomes a priority, the path is a SHA-256 variant of the verifier (separate contract, new domain byte) to leverage hardware hash acceleration.

Retuning

To change the set, edit FORS_K and FORS_A at the top of ForsVerifier.sol. All derived constants (signature layout, loop bounds, bit masks, shift counts) recompute automatically from those two values.

Larger K · A (e.g. K=14, A=13, K·A ≥ 180) keeps high security across many reuses, at the cost of much larger/slower signatures. Prefer it only if the threat model expands to "many signatures per key." A full parameter sweep and rationale lives in docs/fors-parameters.md.