Threshold Lamport Protocol
2-round threshold Lamport OTS over a canonical on-chain intent (no trusted assembler)
Abstract
This LP specifies a public, coordinatorless, 2-round threshold protocol that outputs a standard Lamport one-time signature verifiable by LP-4105. Signers never deliver plaintext shares to a trusted "MPC box". Instead, they post commitments and encrypted contributions to the LUX chain (bulletin board). After ≥t participants contribute for a session, the chain finalizes a Lamport signature for a canonical on-chain WithdrawalIntentHash, which can be relayed and verified on an EVM chain with exactly one Lamport verification.
Key innovation: This protocol solves ALL known threshold Lamport attacks from the EthResearch 2022 discussion and Vitalik's proposals:
- Split-view/equivocation: Canonical on-chain
WithdrawalIntentHashfrom finalized state - Bit harvesting: Single-use
leafIndexwithnextPKHrotation enforced on-chain - Trusted assembler: Encrypted contributions + T-Chain 69-of-100 threshold decryption
- Synchronicity dependence: 2-round chain-based protocol (no N² gossip)
- Missing accountability: Slash-on-failure with forced plaintext reveal
Important correction: the Lamport signature is a set of preimages. Those preimages necessarily become public at finalization. The security goal is that no party learns both branches (0/1 secrets) for a leaf unless ≥t parties collude, and that signers cannot be tricked into signing different messages for the same session.
Motivation
LP-4105 defines Lamport OTS for Lux Safe but assumes a single signer. Production deployments require threshold control where:
- T-of-N parties must cooperate
- No trusted assembler collects plaintext shares
- Anti-equivocation: a malicious leader/block producer cannot show different messages to different signers
- On-chain coordination: chain acts as bulletin board and enforces session rules
- Post-quantum authorization on destination chains (hash-based)
Specification
The threshold Lamport protocol consists of:
- Setup phase for share distribution
- Two-round signing protocol via on-chain coordination
- Threshold decryption for signature finalization
- Accountability mechanism for dispute resolution
Details follow in subsequent sections.
Threat Model and Security Goals
Assume an adversary can:
- control/DoS some signers,
- attempt equivocation (show different messages to different signers),
- attempt to learn enough information to forge signatures for new messages.
Goals
G1 (Anti-equivocation): the only signable digest is derived from finalized on-chain state, so all honest signers necessarily bind to the same message.
G2 (Threshold unforgeability for a leaf): with < t corrupt signers, the adversary cannot compute a valid Lamport signature for any message other than the one finalized by the protocol for that leaf.
G3 (No trusted assembler): no single party receives all plaintext contributions; only the final Lamport signature is revealed publicly.
G4 (Accountability): parties that commit but fail to contribute (or contribute garbage) can be identified and penalized.
Definitions and Notation
| Symbol | Description |
|---|---|
n | committee size (epoch validator set or configured signer set) |
t | threshold required to finalize |
i | Lamport bit index (0..255) |
b | bit value (0 or 1) |
j | signer index / validator |
r[j][i][b] | signer j's secret share for bit i, branch b (32 bytes) |
S[i][b] | leaf secret preimage for bit i, branch b |
PK[i][b] | Lamport public key element = H(S[i][b]) |
H() | keccak256 (or another LP-4105-approved hash) |
leafIndex | Lamport leaf number (single-use) |
epoch | LUX epoch that defines committee membership |
PKH | hash commitment to the Lamport public key (H(PK[...])) |
Canonical Message to Sign (required)
To prevent "different message per signer" attacks, the signable digest MUST be derived from a finalized on-chain withdrawal intent:
WithdrawalIntentHash = keccak256(abi.encode(
bytes32("LUX-WITHDRAW-V1"),
srcChainId,
dstChainId,
burnTxHash,
burnLogIndex,
token,
amount,
recipient,
nonce,
epoch,
leafIndex,
nextPKH
));
epoch,leafIndex,nextPKHare enforced by the LUX contract.- No signer signs an off-chain "txHash" or leader-proposed blob.
Setup Phase (per leaf)
Each Lamport leaf has secrets:
S[i][b] = Σ_j r[j][i][b] mod 2^256
PK[i][b] = H(S[i][b])
Each signer j samples r[j][i][b] ← random(32 bytes) for all i,b and stores only their own shares.
Publishing PKH (required)
The system MUST publish a commitment to the Lamport public key (or a Merkle/XMSS root committing to many leaves). Two acceptable approaches:
Option A (practical, small committees): MPC ceremony among signers to compute PK[i][b] (or PKH) without revealing S[i][b].
Option B (chain-coordinated): signers post commitments to their shares and the protocol includes an audit/dispute mechanism; publication may reveal some data if disputes occur.
This LP standardizes signing/finalization; key-generation ceremony details may be specified in an accompanying implementation note.
Protocol (2 rounds on LUX chain)
The protocol is implemented by a LUX contract (the "Coordinator" in the on-chain sense, not a trusted party). Any account can relay transactions; the contract enforces correctness.
Session Creation
A session is created for a specific WithdrawalIntentHash:
sessionId = keccak256(abi.encode(
bytes32("LUX-LAMPORT-SESSION-V1"),
WithdrawalIntentHash
));
The contract verifies the referenced withdrawal intent exists and is finalized.
Round 1 — Commit (bind signer + bind session)
Each signer j submits:
commit_j = H(
bytes32("LUX-LAMPORT-COMMIT-V1") ||
sessionId ||
epoch || leafIndex ||
WithdrawalIntentHash ||
sharesRoot_j ||
nonce_j
)
Where:
sharesRoot_j = H( r[j][*][*] for this leaf )or a Merkle root over(i,b)→H(r[j][i][b])nonce_jis signer-chosen randomness
Contract rules:
- Accept commits only from the epoch committee.
- Lock the participant set when ≥t commits are posted (or at a deadline).
- Require a bond per signer to deter DoS.
Round 2 — Contribute (message-dependent, encrypted)
Let b_i = bit(WithdrawalIntentHash, i).
Each signer j posts ciphertexts:
ct_j[i] = ENC_to_committee( r[j][i][b_i], sessionId, i )
Requirements:
- Ciphertexts MUST be addressed to a decryption committee corresponding to the epoch (e.g., threshold decryption key held by validators).
- Plaintext shares MUST NOT be posted unless in a dispute.
Finalization — Threshold Reveal of the Lamport signature
When the contract has ≥t valid contributions for all 256 bits, the committee performs threshold decryption/reveal and publishes:
sig[i] = Σ_j r[j][i][b_i] mod 2^256
Signature = sig[0..255]
T-Chain Integration (LP-7000, LP-7330)
The threshold decryption/reveal is performed by the T-Chain (Threshold Chain) validator set:
| Parameter | Value |
|---|---|
| Threshold | 69-of-100 validators |
| VM | ThresholdVM (LP-7330) |
| Decryption Protocol | CGGMP21 / Ringtail |
| Consensus | Quasar with threshold finality |
Decryption flow:
- T-Chain validators receive encrypted contributions
ct_j[i] - Each validator holds a share of the epoch decryption key
- When 69+ validators provide decryption shares, the plaintext
r[j][i][b_i]is reconstructed - Aggregated signature
sig[i]is computed and emitted on-chain
Implementation reference: github.com/luxfi/node/vms/thresholdvm
The contract then emits:
event SignatureFinalized(
bytes32 indexed sessionId,
bytes32 indexed WithdrawalIntentHash,
uint256 epoch,
uint256 leafIndex,
bytes32 nextPKH,
bytes32[256] sig,
bytes32 signersBitmap
);
Lamport verification condition (LP-4105 compatible):
∀i: H(sig[i]) == PK[i][b_i]
Accountability (required)
Encrypted contributions can be garbage. The protocol MUST include an enforceable mechanism to punish non-cooperative or dishonest signers.
A) Slash-on-failure with forced plaintext reveal (recommended default)
If finalization fails (missing bits / invalid signature / decryption failure), the contract opens a dispute window.
For any failing index i, a signer j can reveal:
- plaintext
r[j][i][b_i] - opening data proving it matches
sharesRoot_j(Merkle branch or precommitted hash) - the nonce/opening that binds to
commit_j
The contract verifies the reveal against commit_j material and:
- slashes signers who did not contribute, or
- slashes signers whose ciphertext cannot correspond to a valid committed share.
This is slow but implementable and enforceable.
B) ZK proof of well-formed encryption (optional)
Each ct_j[i] includes a proof that it encrypts a 32-byte value consistent with sharesRoot_j. This reduces disputes but increases complexity.
Key Rotation (required)
Lamport leaves are one-time. Each finalized signature commits to nextPKH.
Rules:
leafIndexMUST be single-use and monotonically increasing.- On finalization, the LUX contract updates
currentPKH = nextPKHatomically. - Destination chain enforces the same rotation when verifying a withdrawal.
Recommended: use an XMSS/Merkle root committing to many Lamport leaves so destination stores one root and each withdrawal carries a Merkle proof for the leaf.
Message Format for Safe / Bridge Use
This LP does not sign arbitrary Safe tx bytes. It signs the canonical on-chain intent hash (anti-equivocation).
If integrating with Safe, the Safe module's "action" MUST be embedded into the intent (e.g., recipient/module/amount fields), and the intent hash is what gets signed.
Rationale
- "No node ever reconstructs a Lamport preimage" is false if interpreted literally: the final Lamport signature is itself a set of preimages. The correct claim is: no single party learns both branch secrets for the leaf, and no trusted party receives all plaintext inputs.
- The canonical intent hash is the decisive fix for the classic "leader sends different messages to different signers" attack.
- Large validator sets should treat encryption/decryption as epoch infrastructure; small signer sets can run the same protocol off-chain (still 2 rounds) without posting ciphertexts on-chain.
Backwards Compatibility
This protocol produces standard Lamport signatures as defined in LP-4105. The on-chain verifier is unchanged:
// Same verification as single-signer Lamport
LamportLib.verify(message, signature, publicKey)
Only the off-chain signing process differs.
Test Cases
Test 1: 2-of-3 Signing
Setup:
- 3 signers generate independent shares
- Public key computed via MPC
Signing:
- Two signers commit
- Two signers contribute
- Signature finalized via threshold reveal
Verification:
- Standard Lamport verify on EVM with LP-4105 verifier returns true
Test 2: DoS - Missing Contribution
Setup:
- Signer commits but doesn't contribute
Expected:
- Timeout triggers dispute window
- Non-contributing signer slashed after timeout
- Remaining signers can restart if ≥ t
Test 3: Garbage Contribution
Setup:
- Signer contributes malformed ciphertext
Expected:
- Dispute reveal proves mismatch
- Signer slashed
- Session can restart with honest signers
Test 4: Equivocation Attempt
Setup:
- Attacker attempts to show different messages to different signers
Expected:
- Impossible: signers only sign WithdrawalIntentHash derived from finalized on-chain state
- No off-chain message proposal accepted
Security Considerations
Preimage Revelation
The final Lamport signature is a set of preimages—these necessarily become public. The security goal is that:
- No party learns both branch secrets (0/1) for any leaf position unless ≥t parties collude
- Signers cannot be tricked into signing different messages for the same session
Complete Resolution of Vitalik/EthResearch Threshold Lamport Critiques
LP-4106 addresses all known attacks from the 2022 EthResearch discussion and Vitalik's "8-choice Lamport" proposal:
| Attack | Description | LP-4106 Defense | Status |
|---|---|---|---|
| Split-View / Equivocation | Malicious BP shows different messages to different signers | Canonical on-chain WithdrawalIntentHash - signers derive message from finalized state only | ✅ SOLVED |
| Bit Harvesting Across Sessions | Attacker learns one branch per bit across multiple sessions | leafIndex is single-use + monotonic; nextPKH enforced on-chain; cannot reuse leaves | ✅ SOLVED |
| 8-Choice Slippage Attack | Vitalik's compression variant vulnerable to partial revelation | We use standard 256-bit Lamport (not 8-choice); each bit independent | ✅ N/A |
| Trusted Assembler | Central party receives all plaintext shares | No assembler; encrypted contributions + T-Chain threshold decryption (69-of-100) | ✅ SOLVED |
| Synchronicity Dependence | Requires N² gossip for signers to agree on message | 2-round protocol via chain bulletin board; no signer-to-signer communication | ✅ SOLVED |
| Missing Accountability | No way to punish non-cooperative signers | Slash-on-failure with forced plaintext reveal; bonds required | ✅ SOLVED |
| Replay / Double-Spend | Same signature used for multiple withdrawals | (epoch, leafIndex, nonce) tuple uniquely identifies session; contract enforces single-use | ✅ SOLVED |
Defense Against Split-View / Equivocation Attack (EthResearch 2022)
The 2022 EthResearch critique of threshold Lamport describes a split-view / equivocation attack:
Each signer reveals only the portion of the Lamport material they're responsible for. A malicious block producer can craft different valid messages B_i and send a different one to each signer such that the particular query bits that signer will reveal match the attacker's target digest H'. The attacker aggregates the returned openings and ends up with a "valid-enough" signature for H' without any signer ever seeing H'.
Root cause: The scheme assumes "everyone is signing the same hash" but does not enforce it. Security depends on synchrony / signers gossiping the hash.
How LP-4106 Solves This Attack
LP-4106 fixes this class of attacks by moving message agreement from "signers talking to each other" to "signers talking to a single canonical bulletin board with finality":
1. The Signable Digest is NOT Leader-Proposed
Signers do not sign "a block / tx hash someone sent me". They sign exactly:
WithdrawalIntentHash = keccak256(abi.encode(... finalized on-chain fields ...))
The contract only allows sessions for intents that exist and are finalized. This means:
- There is exactly one canonical message per intent, derived from chain state
- A malicious relayer/BP cannot give signer A one message and signer B another while still calling it "the same session"
2. Sessions Bind Everyone to the Same Hash
sessionId = H("LUX-LAMPORT-SESSION-V1", WithdrawalIntentHash)
Round 1 commits include sessionId and WithdrawalIntentHash. Once the contract locks the participant set, "the thing being signed" is objectively fixed in public state.
The "send different blocks to different signers" move becomes irrelevant: signers are not signing an off-chain proposal; they are signing the chain-finalized intent hash.
3. The Chain is the Anti-Equivocation Communication Channel
The critique says: "solved if signers talk to each other to ensure they see the same hash".
LP-4106's answer: they don't need to talk to each other; they only need to:
- Read finalized state from the chain
- Post commits/contributions to the chain
The chain provides:
- A single agreed transcript (bulletin board)
- A single message (intent hash) enforced by consensus + contract rules
Attack Failure Analysis
Map the attacker's steps to this protocol:
| Attack Vector | LP-4106 Defense |
|---|---|
| Show signer i a different message off-chain | Signer ignores it. The signer computes WithdrawalIntentHash from finalized on-chain intent fields (or directly from contract storage/events). The contract enforces that the session is tied to that hash. No "per-signer message" surface. |
| Create multiple different intents (one per signer) | Then there are multiple different sessions with different sessionIds. Commit format binds commit_j to sessionId and WithdrawalIntentHash. Contributions are bound to sessionId. The attacker can't mix and match partial openings from different sessions to form a signature for a single target hash. |
| Reuse the same one-time leaf across sessions | leafIndex is single-use + monotonic and contract-enforced rotation via nextPKH. Prevents "harvest openings across multiple sessions until enough is learned". |
The Coordination Guarantee
LP-4106 makes the scheme secure by changing the assumption from "signers are synchronous / gossip hashes" to:
Finality / common-view assumption for the Lux chain: Honest signers derive the signable digest only from finalized on-chain state.
This is the right place to put the assumption, because it's already required for any bridge/safe authorization. If your chain can be equivocating at "finalized state", everything breaks, not just signatures.
Practical spec rule: Commits are only valid after the intent is finalized by the Lux contract (not merely "seen in mempool" / "included in a block").
Why This Solves "Synchronicity Dependence"
The critique says: "the scheme depends on parties communicating with each other → interactive scheme".
LP-4106 accepts "interactive" but makes it 2 rounds via the chain, not N² gossip:
- Round 1: Public commit on-chain (binds signer to the unique canonical message)
- Round 2: Contribute on-chain (bound to that session/message)
- Finalization: Public outcome (Lamport signature or failure with accountability)
This interaction is:
- Public (everyone sees the same thing)
- Globally ordered (chain consensus)
- Enforceable/slashable (bonds and dispute mechanisms)
That is precisely the missing piece in the EthResearch construction.
Threshold Security
With t-of-n threshold:
- Any
tparties can produce a valid signature - Fewer than
tparties cannot forge a signature - Compromising
t-1parties reveals insufficient information for forgery
Anti-Equivocation
The canonical on-chain intent hash prevents the classic attack where a malicious leader shows different messages to different signers. All honest signers bind to the same finalized on-chain state.
Quantum Resistance
Security relies only on:
- Preimage resistance of keccak256
- Security of the threshold protocol
Both are believed quantum-resistant (keccak256 provides 128-bit security against Grover's algorithm).
Accountability and DoS Mitigation
- Bonds required per signer to deter commit-but-don't-contribute attacks
- Dispute mechanism allows identification and slashing of malicious signers
- Session restart possible with different participant set
Related Standards
| LP | Title | Relationship |
|---|---|---|
| LP-4105 | Lamport One-Time Signatures for Lux Safe | Base single-signer Lamport specification |
| LP-7000 | T-Chain Core Threshold Specification | Provides threshold decryption infrastructure |
| LP-7330 | T-Chain ThresholdVM Specification | VM implementation for threshold operations |
| LP-7321 | FROST Threshold Signature Precompile | Alternative threshold scheme (Schnorr) |
| LP-7322 | CGGMP21 Threshold ECDSA Precompile | ECDSA threshold for bridge integration |
| LP-7324 | Ringtail Threshold Signature Precompile | Post-quantum threshold signatures |
| LP-3310 | Safe Multisig Standard | Integration with Lux Safe |
| LP-8100 | FHE Precompiles and Infrastructure | Privacy layer with 69-of-100 threshold |
References
- EthResearch 2022: "Problems with Threshold Lamport" - Analysis of split-view/equivocation attacks on naive threshold Lamport constructions
- Lamport 1979: "Constructing Digital Signatures from a One-Way Function" - Original Lamport OTS paper
- LP-4105: "Lamport One-Time Signatures (OTS) for Lux Safe" - Single-signer Lamport specification
- Vitalik 2022: "8-choice Lamport" variant with slippage compression (also vulnerable to equivocation without chain coordination)
- LP-7000: T-Chain Core Threshold Specification - Threshold signature infrastructure
- LP-7330: T-Chain ThresholdVM Specification - VM for threshold operations