Lux Proposals
← All proposals
LP-0220Draft

Status

Draft. No backwards compatibility. No flag day.

Activated at the genesis of the new final Lux network: **2025-12-25

16:20 Pacific (unix 1766708400)**. The pre-Quasar Edition Lux network

(2020–2025) had no P3Q precompile family and is a separate network out

of scope.

Abstract

LP-218 introduces P3Q at EVM slot 0x012205 — the rollup-batch-aware

PQ verifier family. The precompile takes a 1-byte kind

discriminator at the head of calldata and dispatches to a

primitive-specific P3QVerifier implementation that performs the

threshold-signature verification with an audited per-kind gas charge.

LP-218 specifies kind 0x01 (Pulsar, Module-LWE / FIPS-204 ML-DSA-65

threshold).

A rollup that wants cross-family-maximum PQ posture — for example

custody-grade, treasury, or sovereign-archival rollups — must verify

signatures from independent PQ families so that a structural break

against any one family does not compromise the rollup. LP-220

specifies two additional kinds on the same slot:

| Slot | kind | Name | Family | Verifies |
|---|---|---|---|---|
| 0x012205 | 0x01 | Pulsar (P3Q kind 0x01) | Module-LWE | Pulsar / FIPS-204 ML-DSA-65 threshold |
| 0x012205 | 0x02 | Corona (P3Q kind 0x02) | Module-LWE | Corona threshold signatures |
| 0x012205 | 0x03 | Magnetar (P3Q kind 0x03) | Hash-based | Magnetar / FIPS-205 SLH-DSA threshold |

All three share one slot, one ABI shape, one dispatch path:


// EVM precompile at slot 0x012205 — unified PQ verifier family
function verify(
    uint8 kind,
    bytes calldata sig,
    bytes32 messageHash,
    bytes calldata groupPubKey
) external view returns (bool);

One slot, one address, three primitive kinds. A rollup using PQ-heavy

mode submits one P3Q verify call per kind per RollupBatchTx; a

rollup using PQ-fast mode submits only kind 0x01. Cross-family

resilience is opt-in at the rollup-genesis layer via CertModeFloor

(LP-218 §"Inherited cert mode").

Architectural decision — single slot, kind-byte dispatch

The earlier LP-220 draft allocated separate slots 0x012206 (Corona)

and 0x012207 (Magnetar) for the rollup-batch wrappers and reused

LP-0120's same-address raw primitive verifiers as the underlying

implementation. **2026-06-03 decomplection collapses this into a

single P3Q slot at 0x012205 with kind-byte dispatch.** One slot, one

audit surface, one Solidity ABI shape, one operator-known address,

one dispatch path in EVM. Adding a fourth PQ family (a code-based

threshold sig, say) becomes one new P3QVerifier impl + one new

kind byte; no new precompile slot allocation.

Slots 0x012206 and 0x012207 previously listed in LP-220 drafts

for rollup-batch wrappers are freed: they remain owned by LP-0120

as the raw primitive verifier slots for the Corona and Magnetar

primitives (their original LP-0120 role, distinct from the rollup-

batch family ABI). No part of the LP-218/LP-220 P3Q surface

references those slots after this decomplection.

This honors LP-200 §"One and only one way to do everything." A

Solidity contract that needs Corona-kind verify for a rollup batch

calls P3Q.verify(0x02, sig, h, pk) at 0x012205. A Solidity

contract that needs the raw Corona primitive (a non-rollup-batch use

case) calls the LP-0120 primitive verifier at 0x012206 directly.

The P3Q family ABI is the single canonical entry point for

rollup-batch verification across all PQ kinds.

The unified P3Q precompile — three primitive kinds

One slot, three primitive kinds dispatched on the leading byte:

| Property | kind=0x01 Pulsar | kind=0x02 Corona | kind=0x03 Magnetar |
|---|---|---|---|
| Slot | 0x012205 | 0x012205 | 0x012205 |
| PQ family | Module-LWE (ML-DSA params) | Module-LWE (Ringtail params) | Hash-based |
| Underlying scheme | FIPS-204 ML-DSA-65 | Corona threshold | FIPS-205 SLH-DSA |
| Verifier core | luxfi/pulsar/verify | luxfi/corona/sign.Verify (bespoke; NOT byte-equal to FIPS-204) | luxfi/magnetar/verify (FIPS-205 conformant) |
| Solidity ABI | P3Q.verify(0x01,sig,h,pk)→bool | P3Q.verify(0x02,sig,h,pk)→bool | P3Q.verify(0x03,sig,h,pk)→bool |
| Signature size | 3,309 B | 33,052 B | ~35,664 B |
| Verify time (CPU) | ~80 μs | ~1.6 ms | ~1.92 ms |
| Verify time (Blackwell sm_120) | ~50 μs | ~15 ms | ~50 ms |
| Verify time batched (N=64, Blackwell) | ~5 ms | ~50 ms | ~80 ms |
| Gas cost (P3QVerifier.GasCost()) | 50,000 | 5,000,000 | 30,000 |
| State semantics | stateless | stateless | stateless |
| Constant-time | yes | yes | yes |

Three independent cryptographic foundations:

A simultaneous break would require independent advances in

module-lattice cryptanalysis (Module-LWE, which both Pulsar and Corona

rest on) AND hash function collision attacks (Magnetar). Pulsar and

Corona use different Module-LWE parameter sets and codebases, so a

parameter-specific break need not hit both — but they are not

family-disjoint; only Magnetar provides cross-family independence.

This is the cross-family-maximum posture LP-217's PQ-heavy mode requires.

Architectural decision — common verifier framework

LP-220 introduces a Go-side common abstraction at

~/work/lux/standard/precompiles/p3q_family/:


type P3QVerifier interface {
    // Verify returns true iff sig is a valid threshold signature
    // from groupPubKey on messageHash.
    Verify(sig []byte, messageHash [32]byte, groupPubKey []byte) bool

    // GasCost returns the precompile gas charge for one verify.
    GasCost() uint64

    // Kind returns the calldata discriminator byte (0x01 Pulsar,
    // 0x02 Corona, 0x03 Magnetar).
    Kind() byte

    // PrimitiveName returns "pulsar", "corona", or "magnetar".
    PrimitiveName() string
}

Each P3QVerifier implementation registers at boot into a kind-byte

keyed dispatch table loaded by the single P3Q precompile at slot

0x012205. When the precompile is called, it:

1. Reads the leading kind byte from calldata.

2. Looks up the registered P3QVerifier for that kind.

3. Charges Verifier.GasCost() (per-kind, returned by the impl).

4. Calls Verifier.Verify(sig, messageHash, groupPubKey).

5. Returns the boolean result encoded as a 32-byte EVM ABI bool.

This is the single point of truth for the family — three Verifier

implementations, one slot, one dispatch path, one ABI shape, one

audit surface. Adding a fourth PQ-family kind (a hypothetical

Code-based threshold via HQC, for example) is one new P3QVerifier

impl and one new kind byte registered in the same dispatch table;

no new precompile slot, no new Solidity function shape, no new

audit boundary.

Corona kind (P3Q kind=0x02)

Slot

0x012205 with leading kind=0x02. Same physical precompile

address as Pulsar (LP-218 kind 0x01) and Magnetar (kind 0x03).

Dispatch resolves to the Corona P3QVerifier impl.

Solidity ABI


// EVM precompile at slot 0x012205, kind = 0x02
bool ok = P3Q.verify(
    0x02,                        // Corona kind
    coronaSig,                   // serialized Corona threshold sig
    messageHash,                 // H(prev_root || new_root || batch_hash)
    groupPubKey                  // Corona group public key
);

Wire format

Corona threshold signatures serialize per the Corona spec (not

byte-equal to any NIST standard; Corona is a Lux-defined Module-LWE

threshold scheme). The P3Q precompile reads:

| Offset | Size | Field |
|---|---|---|
| 0 | 1 | kind (= 0x02) |
| 1 | 1 | mode (= 0x01 reserved) |
| 2 | 4 | sigLen |
| 6 | sigLen | sig bytes (Module-LWE polynomial commitments, ~33 KB) |
| 6+sigLen | 4 | groupPubKeyLen |
| 10+sigLen | groupPubKeyLen | groupPubKey |
| 10+sigLen+pkLen | 32 | messageHash |

Gas cost

5,000,000 gas — ~100× the Pulsar kind. Returned by Corona's

P3QVerifier.GasCost() at dispatch.

Per the parity audit's reference benchmark, Corona's verify cost

ratio versus Pulsar's verify cost is approximately 141× on CPU and

~10× on Blackwell sm_120. Corona is genuinely heavier than Pulsar

because Corona verify requires NTT inverse + polynomial multiplication

mod q with larger coefficients (33 KB sig vs 3.3 KB).

The gas calibration uses the CPU verify cost as the baseline (per

LP-218's calibration discipline) — at 1.6 ms CPU vs 80 μs for Pulsar,

the gas ratio is 20× the per-instruction rate. Adjusted for inherent

PQ-verify gas premium (LP-218 §"Honest cost comparison" notes the 10×

factor over BLS), the absolute charge lands at 5,000,000 gas.

This is the most expensive kind in the P3Q family; a rollup that

wants Corona-kind verify per batch should budget its gas accordingly.

Verifier core

The Go reference implementation at

~/work/lux/standard/precompiles/p3q_family/corona.go wraps

luxfi/corona/sign.Verify. The core is bespoke (NOT FIPS-204

byte-equal — Corona is a separate Module-LWE construction, not a NIST

standard).

GPU acceleration at ~/work/lux/accel/cuda/corona.cu provides the

batched-verify path used by LP-203 GPU-native verify for waves of

Corona sigs. The batched kernel amortizes the NTT setup across the

batch (~50 ms for N=64 on Blackwell vs N×15 ms = 960 ms for the

naive serial verifier — 19× speedup).

Use case

A rollup that wants Module-LWE parameter/implementation diversity

configures its sequencer to sign each batch with BOTH a Pulsar

threshold sig AND a Corona threshold sig (two Module-LWE schemes,

distinct parameter sets and codebases). The Z-Chain rollup contract

calls P3Q.verify(0x01, ...) and P3Q.verify(0x02, ...) at the same

precompile address 0x012205 in sequence. A parameter- or

implementation-specific break of Pulsar need not compromise Corona; the

rollup remains cryptographically anchored. For true family-disjoint

protection, add Magnetar (hash).

The cost: ~5,050,000 gas per batch (Pulsar kind 50k + Corona kind 5M).

Acceptable for value-bearing rollups (custody, treasury, regulated

securities); prohibitive for high-volume rollups (gaming, social).

Magnetar kind (P3Q kind=0x03)

Slot

0x012205 with leading kind=0x03. Same physical precompile

address as Pulsar (kind 0x01) and Corona (kind 0x02). Dispatch

resolves to the Magnetar P3QVerifier impl.

Solidity ABI


// EVM precompile at slot 0x012205, kind = 0x03
bool ok = P3Q.verify(
    0x03,                            // Magnetar kind
    magnetarSig,                     // serialized SLH-DSA threshold sig
    messageHash,                     // H(prev_root || new_root || batch_hash)
    groupPubKey                      // Magnetar group public key
);

Wire format

Magnetar threshold signatures are FIPS-205 SLH-DSA conformant at the

underlying-sig level, with a Lux-defined threshold-aggregation wrapper

per LP-181. The P3Q precompile reads the leading kind then the

Magnetar-specific bytes:

| Offset | Size | Field |
|---|---|---|
| 0 | 1 | kind (= 0x03) |
| 1 | 1 | mode (= 0x01 reserved) |
| 2 | 4 | participant_bitmap_len |
| 6 | participant_bitmap_len | bitmap of participating signers (set bits = participating) |
| 6+blen | 4 | slh_sig_len |
| 10+blen | slh_sig_len | SLH-DSA-192f sig bytes (~35,664 B) |
| 10+blen+slen | 4 | groupPubKeyLen |
| 14+blen+slen | groupPubKeyLen | groupPubKey |
| 14+blen+slen+pkLen | 32 | messageHash |

Gas cost

30,000 gas — comparable to the Pulsar kind's 50k. Returned by

Magnetar's P3QVerifier.GasCost() at dispatch.

SLH-DSA verify is fast — ~1.92 ms CPU, ~50 ms on Blackwell (Blackwell

hash-tree walking is bottlenecked by memory bandwidth, not compute).

The sign operation is dramatically slower; see "Parameter set choice"

below.

The 30,000 gas charge reflects the modest CPU verify cost.

Parameter set choice — 192f for production (locked 2026-06-04)

SLH-DSA has two parameter sets at the 192-bit security level:

| Parameter | Sig size | Sign time (Spark) | Sign time (M1 Max) | Production fit |
|---|---:|---:|---:|---|
| SLH-DSA-192s ("small") | ~16 KB | 795 ms | 1,575 ms | breaks 1-s round budget — see LP-217 |
| SLH-DSA-192f ("fast") | ~35 KB | 36 ms | 71 ms | fits inside LP-205 round budget at N≤64 |

The Quasar 4-scheme matrix bench (2026-06-04, /tmp/quasar-matrix-bench)

measured Magnetar-192s as the wall-clock bottleneck for every

PQ-heavy cell — pq-heavy at N=64 on Spark = 1.33 s, blowing past the

1-s sub-second-finality target. Switching to 192f drops the

per-validator sign from 795 ms to 36 ms (22× faster) and restores the

sub-1-s budget.

Production lock: LP-220-conformant deployments MUST configure

Magnetar with SLH-DSA-192f. The threshold-init API at

~/work/lux/threshold/protocols/magnetar/ accepts a parameter-set

argument; pq-heavy chains pass magnetar.ParamSetFast192. Operators

choosing 192s for the 2.2× smaller signature opt into multi-second

finality and MUST run mode=PQ-strict (no Magnetar leg) for the

sub-second tier; 192s is reserved for long-archival anchoring use

cases that don't require fast finality.

Signing for either variant happens off-chain at the sequencer;

on-chain verification under P3Q is fast for both sets (~1.92 ms CPU).

The parameter set affects only the wall-clock of the per-validator

signing ceremony.

Verifier core

The Go reference implementation at

~/work/lux/standard/precompiles/p3q_family/magnetar.go wraps

luxfi/magnetar/verify. The core is FIPS-205 SLH-DSA conformant.

GPU acceleration at ~/work/lux/accel/cuda/magnetar.cu provides

batched verify for waves of SLH-DSA sigs. The batched kernel

amortizes the WOTS+ tree-walk across the batch (~80 ms for N=64).

Use case

A rollup that wants hash-based family independence from lattice

families configures its sequencer to sign each batch with a Magnetar

threshold sig in addition to Pulsar. The Z-Chain rollup contract

calls P3Q.verify(0x01, ...) then P3Q.verify(0x03, ...) at slot

0x012205. SLH-DSA security relies only on the collision resistance

of its underlying hash function (SHA-256 or SHA-3 configurations);

it is immune to advances in lattice cryptanalysis and would survive

even a complete break of the Module-LWE family (both Pulsar and Corona).

Magnetar is the last-line PQ — the kind of choice for

sovereign-archival commitments, long-tenor custody attestations, and

regulator-facing audit trails. The cost (30k gas per verify + 35 KB

signature size) is reasonable for low-frequency, high-value batches.

Composition — PQ-heavy cross-family rollups

A rollup using PQ-heavy mode (LP-217 / LP-218 `CertModeFloor =

0x03`) submits four verifications to its rollup contract — one BLS

classical leg plus three P3Q calls at the same slot, each with a

different kind:

| Verifier | Slot + kind | Family | Gas |
|---|---|---|---|
| BLS aggregate | standard 0x0a (or pairing precompile) | Pairings (classical) | 5,000 |
| P3Q Pulsar | 0x012205 kind=0x01 | Module-LWE | 50,000 |
| P3Q Corona | 0x012205 kind=0x02 | Module-LWE | 5,000,000 |
| P3Q Magnetar | 0x012205 kind=0x03 | Hash-based | 30,000 |

Total: 5,085,000 gas per batch in the cross-family-maximum posture.

A break of any single family does not compromise the rollup batch

because three independent verifications remain. This is the security

ceiling the LP-217 PQ-heavy mode codifies, and LP-220 supplies the

two additional PQ-family kinds on the unified P3Q slot to make it

operationally callable.

The composition is additive: a PQ-heavy rollup contract calls the

BLS precompile and three P3Q.verify(kind, ...) calls at slot

0x012205 in sequence inside its validateBatch function. If any

returns false, the batch is rejected.

PQ-strict (default value-bearing) skips Corona's gas premium

For value-bearing rollups that don't need full cross-family-maximum

but do want PQ insurance, LP-217's PQ-strict mode requires only

BLS + Pulsar + Corona. The rollup contract calls

P3Q.verify(0x01, ...) + P3Q.verify(0x02, ...); gas cost

~5,050,000 per batch.

A rollup operator optimizing for cost may pin strict-PQ-strict

(LP-217 0x11 — drops BLS but keeps Pulsar + Corona). Gas cost

reduces to ~5,050,000 - 5,000 = ~5,045,000 per batch. Marginal

savings; not the optimization point.

PQ-fast (default high-throughput) skips Corona entirely

A high-throughput rollup pinning PQ-fast (LP-218 `CertModeFloor =

0x01`) verifies only Pulsar: gas cost ~50k per batch. The Corona and

Magnetar kinds are not called.

Gas cost calibration

Honest summary of the verify-cost landscape across the P3Q kinds plus

the classical baselines:

| Verifier | Slot + kind | Gas | Verify time CPU | Verify time Blackwell |
|---|---|---|---|---|
| ecrecover (secp256k1) | 0x01 | 3,000 | ~10 μs | ~3 μs |
| BLS pairing | standard | 5,000 | ~6 ms | ~10 μs |
| P3Q Pulsar | 0x012205 0x01 | 50,000 | ~80 μs | ~50 μs |
| P3Q Magnetar | 0x012205 0x03 | 30,000 | ~1.92 ms | ~50 ms |
| P3Q Corona | 0x012205 0x02 | 5,000,000 | ~1.6 ms | ~15 ms |

The Corona kind is the bottleneck. Its CPU verify time (1.6 ms) is

comparable to Magnetar's (1.92 ms), but its gas charge is 167×

Magnetar's. Why the disparity?

Two factors:

1. GPU acceleration ratio. Magnetar verify on Blackwell is

dominated by memory bandwidth (~50 ms for the WOTS+ tree-walk);

Corona verify on Blackwell is compute-bound on the NTT and

parallelizes harder (~15 ms). The GPU win is larger for

Corona than Magnetar in absolute terms — Corona's CPU-to-GPU

ratio is ~107× (1.6 ms / 15 ms = 107), Magnetar's is ~38×

(1.92 ms / 50 ms = 38).

2. Validator hardware assumption. Gas calibration assumes a

CPU-class verifier (per LP-218's calibration discipline —

validators are not required to have Blackwell GPUs). Under that

assumption, Corona's effective per-validator cost is closer to

the audit's measured 1.6 ms ≈ 141× the Pulsar CPU verify time.

The gas charge reflects this: 5M gas vs Pulsar's 50k gas

≈ 100× ratio (rounded for clean accounting).

Operators with all-Blackwell validator sets may petition for a

Blackwell-discounted Corona gas charge in a future amendment;

LP-220 does not codify it. The conservative CPU-baseline gas charge

applies until validator-hardware uniformity is established.

Operator choice — PQ-fast vs PQ-strict vs PQ-heavy

The gas cost ladder makes the LP-217 mode tradeoff explicit:

| Rollup mode | Per-batch gas | Use case |
|---|---|---|
| PQ-off | ~5k (BLS only) | Non-financial; high-throughput; ephemeral state |
| PQ-fast | ~55k (BLS + Pulsar) | Payments, gaming, DEX |
| PQ-strict | ~5,055k (BLS + Pulsar + Corona) | Custody, treasury, regulated securities |
| PQ-heavy | ~5,085k (BLS + Pulsar + Corona + Magnetar) | Sovereign anchors, cross-chain bridges, long-archival |

The 100× cost cliff between PQ-fast and PQ-strict is intentional —

Corona's gas charge naturally prices cross-family insurance. A

rollup that does not need Module-LWE-parameter-diversity (not family-independence pays only

PQ-fast prices; a rollup that does, pays PQ-strict prices.

This is the canonical Pareto frontier the LP-217 cert profile modes

codify. LP-220 just supplies the additional P3QVerifier kinds on

the unified P3Q slot 0x012205 that make each mode operationally

callable from a rollup contract.

Failure modes

| Failure | Trigger | Effect | Recovery |
|---|---|---|---|
| Unknown kind byte | caller passes kind not in {0x01, 0x02, 0x03} | P3Q returns false; rollup contract may revert | caller fix; activation predates any live rollup so no backwards-compat layer |
| Corona-kind sig malformed | sequencer bug or bad input | P3Q.verify(0x02,...) returns false; rollup contract may revert | client re-submits with valid sig; sequencer fix |
| Magnetar-kind sig malformed | sequencer bug or bad input | P3Q.verify(0x03,...) returns false; rollup contract may revert | client re-submits with valid sig |
| Corona group pubkey mismatch | sequencer rotation without group-pubkey update on Z-Chain | P3Q.verify(0x02,...) returns false for all subsequent batches until rotation tx lands | sequencer rotation tx updates group pubkey; backfill batches re-verify |
| Gas-budget exhaustion (PQ-heavy on small batch) | aggregated tx contains too few transactions to amortize the 5M Corona-kind verify | rollup batch unprofitable | rollup operator increases minimum batch size; Corona-kind verify amortizes across more txs |
| Z-Chain validator missing a P3Q kind impl | misconfigured validator, missing P3QVerifier registration | validator fails to validate batch; rollback to LP-201 normal-path | validator config audit; operator rebuilds with full P3Q dispatch table |
| Cross-kind precompile timing skew | one verifier hangs (e.g. Magnetar-kind GPU stall) | rollup contract gas runs out | timeout per call; revert; client re-submits |

Composition with other LPs

| LP | Role |
|---|---|
| LP-073 | Pulsar threshold signature — P3Q kind 0x01 verifies |
| LP-181 | Magnetar threshold SLH-DSA — P3Q kind 0x03 verifies |
| LP-182 | QuasarCert leg composition reflects the verifier kinds |
| LP-200 | ZAP Stack — buffer-is-the-value applies to PQ-family verifies |
| LP-202 | Cert tier degradation — Corona and Magnetar P3Q kinds are observability anchors for higher tiers |
| LP-203 | Batched GPU verify for Corona and Magnetar via ~/work/lux/accel/cuda/ kernels |
| LP-217 | PQ-heavy mode requires all three P3Q kinds; PQ-strict requires Pulsar+Corona; PQ-fast requires Pulsar only |
| LP-218 | The P3Q precompile family at slot 0x012205 — LP-218 specifies kind 0x01 (Pulsar) and the unified ABI; LP-220 specifies kinds 0x02 (Corona) and 0x03 (Magnetar) on the same slot |
| LP-219 | Cross-rollup atomic batches verify per-rollup with the kinds chosen by each rollup's CertModeFloor |
| LP-0120 | Quasar mainnet defaults — slot registry. Owns 0x012204 (raw Pulsar), 0x012206 (raw Corona), 0x012207 (raw Magnetar) for non-rollup-batch use; LP-218/LP-220's P3Q family lives exclusively at 0x012205. |
| FIPS 205 | SLH-DSA standard — Magnetar-kind verifier conformant |
| FIPS 204 | ML-DSA standard — Pulsar-kind verifier conformant |

LP-220 completes the on-chain PQ-verify surface. There is no fourth

PQ family in the current Quasar stack — Code-based KEMs (HQC) are

KEMs, not signature schemes; their on-chain role per LP-0120 is

key encapsulation, not batch authentication. A hypothetical

Code-based-signature PQ family (Hamming-Quasi-Cyclic-Sig, McEliece-

Sig variants) would add a fourth kind byte on the same P3Q slot

0x012205 if it ever entered the Lux stack; LP-220's P3QVerifier

framework would absorb it without a new slot allocation.

Activation marker


activates: 2025-12-25T16:20:00-08:00
activates-unix: 1766708400

P3Q at slot 0x012205 with kinds 0x02 (Corona) and 0x03 (Magnetar)

is callable from the genesis block of the new final Lux network.

Rollup deployments that target PQ-strict or PQ-heavy mode

(CertModeFloor = 0x02 or 0x03) call P3Q.verify(kind, ...) from

height 0 with the appropriate kind byte.

Cross-references