Forty-five of the fifty-six suite modules keyed off <name>Config and eleven did not, in three other spellings: Verify, Threshold, Encapsulate. The LP-4200 unified PQCrypto block carried four of those conventions across its eight addresses — two KEMs named differently (mlkemConfig beside hqcEncapsulate) and two threshold schemes named differently (pulsarVerify beside coronaThreshold) — so the block that exists to be one thing did not read as one thing. The deviations also named an OPERATION where a precompile belongs. HQC decapsulates as well as encapsulates and the Verify precompiles do more than verify, so hqcEncapsulate was the name of one entry point standing in for the whole module. A config key selects a precompile; it is not a method selector. All fifty-six are <name>Config now. There is no compatibility shim: an upgrade.json naming an old key is naming something that does not exist, which luxd already refuses loudly, and every chain config in the estate moves with this. Co-authored-by: Hanzo Dev <[email protected]>
Pulsar Threshold Signature Precompile
Post-quantum threshold signature verification precompile for Lux EVM, implementing the LWE-based two-round threshold signature scheme from Pulsar (ePrint 2024/1113).
Overview
The Pulsar Threshold precompile enables verification of lattice-based threshold signatures on-chain, providing post-quantum security for multi-party consensus protocols. This is a critical component of the Quasar quantum consensus mechanism.
Precompile Address: 0x020000000000000000000000000000000000000B
Features
- Post-Quantum Security: Based on Learning With Errors (LWE) lattice problem
- Threshold Signatures: Supports t-of-n threshold schemes
- Two-Round Protocol: Efficient distributed signing
- Quasar Integration: Native support for quantum consensus validators
- Flexible Thresholds: Configurable from 1-of-n to n-of-n
Algorithm
Pulsar implements a threshold signature scheme based on:
- Lattice Cryptography: LWE problem hardness
- Module Learning With Errors: Polynomial ring operations
- Threshold Secret Sharing: Shamir's secret sharing over rings
- Two-Round Protocol: Efficient distributed key generation and signing
Parameters
From corona/sign/config.go:
| Parameter | Value | Description |
|---|---|---|
| M | 8 | Matrix rows |
| N | 7 | Matrix columns |
| Dbar | 48 | Signature dimension |
| Q | 0x1000000004A01 | Prime modulus (48-bit) |
| LogN | 8 | Ring dimension log (256) |
| SigmaE | 6.108 | Error distribution |
| SigmaStar | 1.73e11 | Masking distribution |
Security Level
- Classical: >128-bit security
- Quantum: Resistant to Shor's algorithm
- Hardness: Based on LWE with modulus Q and dimension 256
Gas Costs
| Operation | Formula | Example |
|---|---|---|
| Base Cost | 150,000 | Fixed cost |
| Per Party | 10,000 × n | Cost per party |
| 2 parties | 150,000 + 20,000 | 170,000 gas |
| 3 parties | 150,000 + 30,000 | 180,000 gas |
| 5 parties | 150,000 + 50,000 | 200,000 gas |
| 10 parties | 150,000 + 100,000 | 250,000 gas |
Input Format
Offset | Size | Field
--------|------|------------------
0-4 | 4 | threshold (uint32)
4-8 | 4 | totalParties (uint32)
8-40 | 32 | messageHash (bytes32)
40-... | ~4KB | signature (variable)
Signature Components
The signature consists of serialized lattice elements:
- c: Challenge polynomial (256 bytes)
- z: Response vector (N × 256 bytes = 1,792 bytes)
- Delta: Difference vector (M × 256 bytes = 2,048 bytes)
- A: Public matrix (M × N × 256 bytes = 14,336 bytes)
- bTilde: Rounded vector (M × 256 bytes = 2,048 bytes)
Total: ~20KB per signature
Usage
Solidity
import "./ICoronaThreshold.sol";
contract MyContract is CoronaThresholdVerifier {
function verifyConsensus(
bytes32 messageHash,
bytes calldata signature
) external view returns (bool) {
// 2-of-3 threshold
return verifyThresholdSignature(
2, // threshold
3, // total parties
messageHash,
signature
);
}
function processSignedData(
bytes32 messageHash,
bytes calldata signature
) external {
// Revert if invalid
CoronaThresholdLib.verifyOrRevert(
2, 3, messageHash, signature
);
// Process data - signature is valid
}
}
TypeScript/ethers.js
import { ethers } from 'ethers';
const CORONA_THRESHOLD = '0x020000000000000000000000000000000000000B';
const abi = [
'function verifyThreshold(uint32,uint32,bytes32,bytes) view returns(bool)'
];
const precompile = new ethers.Contract(CORONA_THRESHOLD, abi, provider);
// Verify 2-of-3 threshold signature
const isValid = await precompile.verifyThreshold(
2, // threshold
3, // total parties
messageHash, // bytes32
signatureBytes // bytes
);
Go
import (
"corona/sign"
"github.com/luxfi/lattice/v6/ring"
)
// Generate threshold signature (off-chain)
threshold := 2
totalParties := 3
// Initialize rings
r, _ := ring.NewRing(1<<sign.LogN, []uint64{sign.Q})
r_xi, _ := ring.NewRing(1<<sign.LogN, []uint64{sign.QXi})
r_nu, _ := ring.NewRing(1<<sign.LogN, []uint64{sign.QNu})
// Run threshold signing protocol
// ... (see corona/main.go for full example)
// Verify signature
valid := sign.Verify(r, r_xi, r_nu, z, A, mu, bTilde, c, Delta)
Protocol Flow
Setup Phase
- Trusted dealer generates keys using
sign.Gen() - Secret shares distributed to n parties
- Public parameters (A, b) published
Signing Phase (Two Rounds)
Round 1: Each party generates masking polynomials
D_i, MACs_i := party.SignRound1(A, sid, PRFKey, T)
Round 2 Preprocess: Verify MACs and combine D matrices
valid, DSum, hash := party.SignRound2Preprocess(A, b, D, MACs, sid, T)
Round 2: Each party generates signature share
z_i := party.SignRound2(A, bTilde, DSum, sid, mu, T, PRFKey, hash)
Finalize: Combiner aggregates shares
c, z_sum, Delta := party.SignFinalize(z, A, bTilde)
Verification
valid := sign.Verify(r, r_xi, r_nu, z, A, mu, bTilde, c, Delta)
Quasar Consensus Integration
The Pulsar threshold precompile is designed for Quasar consensus:
contract QuasarConsensus {
uint32 constant VALIDATORS = 5;
uint32 constant THRESHOLD = 4; // 4-of-5 for finality
function finalizeBlock(
bytes32 blockHash,
bytes calldata validatorSignature
) external {
// Verify 4-of-5 validators signed
CoronaThresholdLib.verifyOrRevert(
THRESHOLD,
VALIDATORS,
blockHash,
validatorSignature
);
// Block is finalized with quantum-resistant proof
emit BlockFinalized(blockHash);
}
}
Security Considerations
Post-Quantum Security
- Quantum Attacks: Resistant to Shor's algorithm
- Classical Attacks: >128-bit security under LWE assumption
- Side Channels: Constant-time operations in lattice library
Threshold Properties
- Unforgeability: Cannot forge signature without t parties
- Robustness: Works with any subset of size t
- Non-interactivity: After setup, signing is non-interactive per party
Network Security
- MAC Protection: Message authentication codes prevent tampering
- Replay Protection: Session IDs prevent signature replay
- Rank Checks: Full rank verification prevents malicious matrices
Performance
Benchmarks (Apple M1 Max)
| Operation | Time | Gas |
|---|---|---|
| 2-of-3 verify | ~2.5ms | 170,000 |
| 3-of-5 verify | ~3.8ms | 200,000 |
| 5-of-7 verify | ~5.2ms | 220,000 |
Comparison with Other Schemes
| Scheme | Signature Size | Verify Time | Quantum Secure |
|---|---|---|---|
| ECDSA (secp256k1) | 65 bytes | ~88μs | ❌ |
| BLS | 96 bytes | ~2.1ms | ❌ |
| Pulsar | ~20KB | ~3.8ms | ✅ |
| ML-DSA-65 | 3.3KB | ~108μs | ✅ |
| SLH-DSA | 7.9KB | ~4.2ms | ✅ |
Trade-offs:
- ✅ Post-quantum security
- ✅ Threshold signatures
- ✅ Two-round protocol
- ⚠️ Large signature size (~20KB)
- ⚠️ Higher verification time
Testing
# Run tests
cd /Users/z/work/lux/standard/src/precompiles/corona-threshold
go test -v
# Run benchmarks
go test -bench=. -benchmem
# Test with specific threshold
go test -run TestCoronaThresholdVerify_3of5
Test Coverage
- ✅ 2-of-3 threshold signature
- ✅ 3-of-5 threshold signature
- ✅ Full threshold (n-of-n)
- ✅ Invalid signature rejection
- ✅ Wrong message rejection
- ✅ Threshold not met rejection
- ✅ Input validation
- ✅ Gas cost calculation
Migration from Classical Signatures
From ECDSA
// Before (ECDSA)
function verify(bytes32 hash, bytes memory sig) public view {
address signer = ECDSA.recover(hash, sig);
require(isAuthorized(signer), "Unauthorized");
}
// After (Pulsar Threshold)
function verify(bytes32 hash, bytes memory sig) public view {
CoronaThresholdLib.verifyOrRevert(
THRESHOLD,
TOTAL_SIGNERS,
hash,
sig
);
}
From BLS
// Before (BLS aggregate)
function verify(bytes32 hash, bytes memory sig) public view {
require(BLS.verify(aggregateKey, hash, sig), "Invalid");
}
// After (Pulsar Threshold)
function verify(bytes32 hash, bytes memory sig) public view {
require(
verifyThresholdSignature(THRESHOLD, N, hash, sig),
"Invalid"
);
}
References
License
Copyright (C) 2025, Lux Industries, Inc. All rights reserved.