Why One-Time Signature Keys Must Never Be Reused
One-time signatures have an unusual operating rule whereby a successful signature consumes a signing opportunity. The key is not simply a password that can be used again whenever the previous operation succeeded. Reusing it can reveal material that allows an observer to construct a signature the owner never produced.
Our existing Lamport and Winternitz articles explain the underlying constructions. Here we isolate the key-reuse mistake with a tiny executable example. It uses Sphere 10 Framework's hashing and random-byte utilities, but deliberately signs four-bit values so every exposed choice can be followed by inspection.
Contents
Follow One Bit Position
For each position, prepare two secret values: one for zero and one for one. Publish their hashes. A signature reveals the secret selected by the message bit, which the verifier hashes and compares with the corresponding public value.
secrets[position][bit] = Tools.Crypto.GenerateCryptographicallyRandomBytes(32);
publicValues[position][bit] = Hashers.Hash(CHF.SHA2_256, secrets[position][bit]);
Someone who sees only the zero alternative at that position does not thereby learn the one alternative. That separation is part of the construction. The complete downloadable C# project prepares four positions, signs a value and verifies the result using only the public hashes.
Reuse the Key and Observe What Changes
The first signature covers 0101. The same key then signs 1010. Those values choose opposite alternatives at every position. Between the two signatures, an observer now holds both alternatives for each position, without learning them through a hash inversion.
To forge 1111, select the revealed one alternative from whichever observed signature contains it. The verifier cannot tell whether the owner assembled those values specifically for 1111 or whether an observer assembled them from the two earlier signatures.
var first = Sign(0b0101, secrets);
var second = Sign(0b1010, secrets);
for (var position = 0; position < 4; position++) {
forged[position] = ((0b0101 >> position) & 1) == 1
? first[position] : second[position];
}
Run the Demonstration
dotnet add package Sphere10.Framework --version 3.1.3
dotnet run --project Example.csproj
First signature verifies: True
Unsigned 1111 verifies after key reuse: True
The second line is the defect being illustrated. The signer did not sign 1111. The example exposes enough alternatives to construct it. For real digest sizes and other reused message pairs, the available choices differ. This example isolates the mechanism without claiming that every pair reveals every possible forgery.
State Management Belongs to the Signing Design
Practical stateful hash-based systems arrange many one-time opportunities beneath a larger public key. Their implementation must prevent a consumed opportunity from being used again. Parallel signers, retries, restored backups and copied state are therefore part of the security boundary, not merely operational conveniences.
A retry after uncertain delivery is especially different from a fresh signature request. The application needs to know which signing state was already consumed and what was sent. A backup procedure that rolls a signer back to an earlier index can reintroduce the very reuse the scheme requires it to avoid.
For the standardised stateful constructions, consult RFC 8554 , RFC 8391 and NIST SP 800-208 . Revisit the existing Lamport article and Winternitz article for the broader explanation. The precise lesson is to preserve the one-time contract even when the surrounding application retries, recovers or scales out.
Continue the Series
Hashes, Signatures and Merkle Proofs: What Does Each Actually Prove?
How to Prove One Record Belongs to a Dataset
For the broader Sphere 10 development toolkit, see the Sphere10 Framework overview .