corona@v0.10.3 renamed GenerateKeys→GenerateKeysTrustedDealer (identical sig). Restores precompile ./... green; unblocks PR #8. Co-authored-by: Hanzo Dev <dev@hanzo.ai>
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.