Skip to content

Security: mkrzyzan/sealstore

Security

SECURITY.md

Security

Known properties, limitations, and open issues in the WOTS-signed payment scheme. This supersedes the previous commit–reveal scheme, which was removed.

Why the substitution attack does not land

A malicious miner who sees a broadcast SignPaymentTx cannot substitute their own transaction:

  1. The signature binds the exact body and rotation target. The WOTS signature is computed over Hash(Encode(payment_body) || next_root). Any change to from, seq, transfers, fee, or next_root changes 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).
  2. 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.

Why the rotation target cannot be swapped

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.

Why a spent key cannot be reused

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 at seq+1) and increments seq; the spent key index can never authorise again because the stored root has moved on.

Forging by raising chunk values is blocked

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.

WOTS+ (keyed, masked chains)

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.

Open issue 1 — Root does not pin the account's current state

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.

Accepted limitations

  • Master-seed compromise is out of scopeS must 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 seq anyway.

There aren't any published security advisories