Known properties, limitations, and open issues in the WOTS-signed payment scheme. This supersedes the previous commit–reveal scheme, which was removed.
A malicious miner who sees a broadcast SignPaymentTx cannot substitute their
own transaction:
- The signature binds the exact body and rotation target. The WOTS
signature is computed over
Hash(Encode(payment_body) || next_root). Any change tofrom,seq,transfers,fee, ornext_rootchanges the signed digest, and SHA-256 preimage resistance means the attacker cannot produce a valid signature for a different message (they would need the account's WOTS secret for this key index). - Verification needs no separate public key. The chain reconstructs the
per-chain public values from the signature and requires them to hash to the
stored root
P; a substituted body fails this check.
next_root is folded into the signed digest, so it is authenticated by the
signature just like the body. An attacker who sees a valid SignPaymentTx
cannot replace next_root with garbage and rebroadcast — that would change the
digest and the signature would no longer verify. Without this, an attacker could
trick the chain into rotating P to an unknown root and permanently lock the
account; the signed digest closes that hole.
Each key index signs exactly one transaction:
- The signature reveals only partial chain values (
c_{ChainLen-v}(sk)under the WOTS+ keyed, masked chain). - On success the account rotates
P := next_root(the key atseq+1) and incrementsseq; the spent key index can never authorise again because the stored root has moved on.
Raising message chunk values so fewer hashes are needed to reach the public
value is the classic WOTS forgery. The base-16 checksum over the message
chunks makes this impossible: raising message values lowers the checksum, so at
least one chain would have to be advanced — which an attacker cannot do without
the secret. The checksum is included in DefaultParams.
This chain uses WOTS+ (the inner one-time signature of NIST FIPS 205 /
SPHINCS+), not classic WOTS: each chain step is F(K, r_i, x) = sha256(K || (x XOR r_i))[:n] with a public constant key K, per-position public
masks r_i, and an internal chain length n = 16. Randomising each step with a
mask makes it harder to amortise one-way-function preimages across many chain
positions and keys, closing the multi-target attacks that apply to a plain
iterated hash.
For the specification and the security argument behind the keyed, masked WOTS+ construction, see:
- NIST FIPS 205 (2024), Stateless Hash-Based Digital Signature Standard (SLH-DSA, based on SPHINCS+, of which WOTS+ is the inner OTS): https://doi.org/10.6028/NIST.FIPS.205
- A. Hülsing, WOTS+ – Shorter Signatures for Hash-Based Signature Schemes (proof of strong unforgeability under chosen-message attacks in the standard model): https://eprint.iacr.org/2017/965
The internal chain length is half the message digest length: the digest
(DigestLen = 32) needs collision resistance (birthday bound 2^128), while
the chain only needs one-wayness. Because WOTS+ steps are keyed and masked, an
n = 16 chain does not weaken the scheme the way a 16-byte chain would in
classic WOTS. Parameters: w=8, n=16, DigestLen=32.
P is a commitment to the current key's per-chain public values but does not
itself encode seq or balance. Replay is prevented by the seq check and
rotation; this is the same trust model as before and is considered acceptable.
- Master-seed compromise is out of scope —
Smust be protected off-chain; anyone with the seed can derive every WOTS key and spend the account. - One-time signature; no re-keying within a seq. A wallet that signs two
different bodies with the same key index (e.g. a client bug) leaks enough
partial chains that the key should be treated as compromised; the chain
rejects the second
seqanyway.