Lux Proposals
← All proposals
LP-0218Draft

Status

Draft. No backwards compatibility. No flags. No replay.

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

16:20 Pacific (unix 1766708400)**. Predates every rollup block. The

pre-Quasar Edition Lux network (2020–2025) had no P3Q precompile and is

a separate network out of scope.

Abstract

A ZAP-native PQ rollup is a Tier-3 chain (per LP-204) whose sequencer

collects N transactions as ZAP frames, applies them against the rollup

state, computes a new state root over the ZAP-buffer Merkle structure

defined in LP-203, and signs the tuple `(prev_root, new_root,

batch_hash, sequencer_id)` with a Pulsar threshold signature (LP-073).

The sequencer submits `RollupBatchTx{prev_root, new_root, batch_hash,

pulsar_sig}` as a ZAP frame to Z-Chain (the parent L1 Tier-1 hosting

the rollup-VM at Tier-3 in the LP-204 hierarchy).

Z-Chain validators verify the PQ signature via the **P3Q precompile

at EVM slot 0x012205**. P3Q is the on-chain verifier family defined in

HANZO-CRYPTO-SUITE §5.2 — a single precompile that takes a kind byte

(0x01 Pulsar, 0x02 Corona, 0x03 Magnetar), a threshold signature, a

message hash, and a group public key, and returns true iff the

signature validates under the named PQ family. One slot, one ABI,

three primitive kinds dispatched by leading byte (LP-220 specifies the

Corona and Magnetar uses of this same slot). The verification is part

of normal Z-Chain block validation; no second consensus is needed. The

Z-Chain block then finalizes via the parent L1's QuasarCert (LP-202

tier degradation, named per LP-217: PQ-off → PQ-fast → PQ-strict →

PQ-heavy; was "BLS preliminary → Pulsar → Aurora → Polaris" in LP-017).

The rollup state inherits the parent L1's full cryptographic finality

because the rollup commit IS a Z-Chain transaction and Z-Chain IS the

parent L1's rollup substrate (LP-063 + LP-204). No rollup-side

validator set exists. No fraud-proof bond is required for the

cryptographic path — the Pulsar signature is sufficient for the

authenticity of the state transition; an optimistic-rollup challenge

window is only needed if a fraud-proof system is the integrity model

of the execution itself.

The pattern — 6 steps


┌───────────────────────────────────────────────────────────────────┐
│ Rollup sequencer (Tier-3 — no validator set, single or threshold) │
└────────────────────────────────┬──────────────────────────────────┘
                                 │
   1. Collect N txs (each ZAP frame, no re-encode)
                                 │
   2. Apply tx batch → compute new state root (LP-203 GPU Merkle)
                                 │
   3. Pulsar threshold-sign (prev_root, new_root, batch_hash, seq_id)
                                 │
   4. Submit RollupBatchTx as ZAP frame to Z-Chain
                                 │
                                 ▼
┌───────────────────────────────────────────────────────────────────┐
│ Z-Chain validators (parent L1 Tier-1 — full QuasarCert validators)│
└────────────────────────────────┬──────────────────────────────────┘
                                 │
   5. P3Q(0x012205) verify PQ sig (kind ∈ {Pulsar/Corona/Magnetar})
      over H(prev_root‖new_root‖batch_hash)
                                 │
   6. Z-Chain block finalizes via parent L1 QuasarCert
      → rollup state inherits parent L1 finality

Stage by stage:

| # | Stage | Where it runs | Cost target |
|---|---|---|---|
| 1 | Tx collection | Sequencer node(s) | NIC throughput-bound, ZAP frames flow at LP-201 wire speed |
| 2 | State transition + Merkle root | Sequencer GPU (LP-203 UMA buffer) | ~100 μs per batch at 1k txs |
| 3 | Pulsar threshold sign | Sequencer group (1-of-1 or t-of-n) | ~5 ms at N=64 on H100 (per LP-203 Pulsar bench row) |
| 4 | RollupBatchTx submission | ZAP frame over QUIC (LP-201) | ~1 ms RTT to nearest Z-Chain validator |
| 5 | P3Q verify on Z-Chain | Z-Chain validator EVM precompile | ~50 μs per verify on Blackwell sm_120 |
| 6 | Parent L1 finality | Z-Chain QuasarCert | LP-217 cert mode — PQ-fast ≈ 5 ms, PQ-strict ≈ 15 ms |

End-to-end commit latency from sequencer batch close to parent L1

finality at PQ-strict: ~10–50 ms. End-to-end at PQ-off (BLS only):

~1–5 ms.

P3Q precompile interface


// EVM precompile at slot 0x012205 — unified PQ verifier family.
// One slot, one dispatch, one ABI shape across all primitive kinds.
//
// kind:
//   0x01  Pulsar     (Module-LWE, FIPS-204 ML-DSA-65, LP-073)
//   0x02  Corona     (Module-LWE threshold, LP-220 §"Corona kind")
//   0x03  Magnetar   (hash-based, FIPS-205 SLH-DSA-192f threshold,
//                     LP-181 + LP-220 §"Magnetar kind")
//
// sig and groupPubKey are primitive-specific. Lengths are
// verified per-kind against the underlying scheme's canonical sizes.
function verify(
    uint8 kind,                   // 0x01 / 0x02 / 0x03
    bytes calldata sig,           // primitive-specific sig bytes
    bytes32 messageHash,          // H(prev_root || new_root || batch_hash)
    bytes calldata groupPubKey    // primitive-specific group pubkey
) external view returns (bool);

Properties:

| kind | Primitive | Gas |
|---|---|---|
| 0x01 | Pulsar (ML-DSA-65 threshold) | 50,000 |
| 0x02 | Corona (M-LWE threshold) | 5,000,000 |
| 0x03 | Magnetar (SLH-DSA-192f threshold) | 30,000 |

Pulsar's 50k figure is the 10× factor over BLS pairing verify

(~5,000 gas) that reflects the inherent cost of lattice verification

— ML-DSA verify requires NTT inverse + matrix-vector multiplication

mod q. Corona's 5M reflects Module-LWE's larger sig (33 KB) and

CPU-class verify cost ~1.6 ms; Magnetar's 30k reflects ~1.92 ms CPU

verify amortized cleanly over a memory-bandwidth-bound WOTS+ walk.

See LP-220 §"Gas cost calibration" for the cross-kind ladder.

Verifier reference

| Artifact | Path | Purpose |
|---|---|---|
| Go reference impl | ~/work/lux/precompile/p3q/contract.go | EVM precompile implementation registered at slot 0x012205 |
| Go module registration | ~/work/lux/precompile/p3q/module.go | init() registers p3qVerify config key + slot 0x012205 |
| Go unit + integration tests | ~/work/lux/precompile/p3q/contract_test.go | 18 tests: address pin, gas formula, OOG, structural rejection, real FIPS 204 round-trip across MLDSA44/65/87, wrong-hash / wrong-pk / corrupted-sig rejection, domain-separation replay defense, EncodeInput round-trip |
| Solidity consumer ABI | ~/work/lux/precompile/p3q/IP3Q.sol | IP3Q interface + P3QLib helper library (verify, verifyOrRevert, encode, estimateGas) for Solidity callers |
| Pulsar verifier core | github.com/luxfi/crypto/mldsa (mldsa.VerifySignatureCtx) | Constant-time FIPS 204 ML-DSA verifier — P3Q dispatches to this; Pulsar threshold-signature output is byte-equal so the same verifier accepts both |
| Reference pulsar package | ~/work/lux/threshold/protocols/pulsar (re-exports ~/work/lux/pulsar/ref/go/pkg/pulsar) | Threshold-sign producer side; consensus consumers use pulsar.VerifyCtx for the same FIPS 204 verification but the precompile uses mldsa.VerifySignatureCtx directly to avoid an unnecessary module hop |
| GPU kernel (planned) | ~/work/lux/accel/cuda/p3q.cu | Blackwell sm_120 batched verify path (LP-203 GPU verify dispatch) |

P3Q's verifier core IS the FIPS-204 ML-DSA verifier — Pulsar signatures

serialize byte-identically to FIPS-204 ML-DSA signatures per LP-073

wire format. Any conformant FIPS-204 verifier suffices. The precompile

exists to give EVM-side callers a stable ABI and an audited gas charge.

Precompile wire format (canonical)

Single-call interface; no Solidity function selector. The wire bytes

begin with a 1-byte kind discriminator that selects which

P3QVerifier impl handles the call:


[ 1 byte  ] kind             — 0x01 Pulsar | 0x02 Corona | 0x03 Magnetar
[ 1 byte  ] mode             — primitive-specific (Pulsar: 0x44/0x65/0x87
                               for ML-DSA-44/-65/-87; Corona: 0x01 reserved;
                               Magnetar: 0x01 reserved)
[ 4 bytes ] sigLen           — big-endian uint32
[ N bytes ] sig              — N = sigLen, must equal the kind+mode's
                               canonical sig size
[ 4 bytes ] groupPubKeyLen   — big-endian uint32
[ M bytes ] groupPubKey      — M = groupPubKeyLen, must equal the kind+mode's
                               canonical pk size
[ 32 bytes ] messageHash     — fixed; same across kinds

Output: 32-byte EVM-ABI bool. Last byte 0x01 on a verifying signature,

0x00 on any verification failure (unknown kind, malformed input,

mode mismatch, length mismatch, primitive verify reject). The precompile

NEVER reverts on cryptographic failure — the Solidity caller decides

whether false is fatal.

The Solidity-side encoder is P3QLib.encode(kind, mode, sig, groupPubKey, messageHash)

in IP3Q.sol. The Go-side encoder is p3q.EncodeInput(kind, mode, sig, groupPubKey, messageHash).

Both produce byte-identical output for the same inputs across all kinds.

Domain separation

Every P3Q verify binds the FIPS 204 context string

lux-evm-precompile-p3q-v1. Mismatched context vs signer causes

rejection — prevents cross-precompile replay where a Pulsar signature

minted for the generic 0x012204 Pulsar slot (context

lux-evm-precompile-pulsar-v1) is re-submitted to the rollup-commit

0x012205 P3Q slot. Signers producing rollup-commit signatures MUST

set ctx = "lux-evm-precompile-p3q-v1" (see pulsar.SignCtx /

mldsa.SignCtx in the producer paths).

The unit test TestP3Q_RejectsWrongContextDomainSeparation in

~/work/lux/precompile/p3q/contract_test.go pins this: a signature

under the Pulsar-slot context is rejected by P3Q.

Gas cost calibration

Gas is charged per kind by P3QVerifier.GasCost() (LP-220 §"Architectural

decision — common verifier framework"). The per-byte adder is 10 gas/byte

of input across all kinds. Base charge by kind:

| kind | Base gas | CPU verify | Blackwell sm_120 |
|---|---:|---:|---:|
| 0x01 Pulsar (ML-DSA-65 threshold) | 50,000 | ~80 µs | ~50 µs |
| 0x02 Corona (M-LWE threshold) | 5,000,000 | ~1.6 ms | ~15 ms |
| 0x03 Magnetar (SLH-DSA-192f threshold) | 30,000 | ~1.92 ms | ~50 ms |

LP-218 §"Honest cost comparison" sets the Pulsar base at 50,000 gas;

LP-220 sets Corona at 5M and Magnetar at 30k. See LP-220 §"Gas cost

calibration" for the cross-kind ladder and the validator-hardware

assumption behind it.

Measured Pulsar CPU verify time on M1 Max / DGX Spark (Pulsar parity

audit 2026-06-03, harness at lux/threshold/protocols/parity/parity_test.go):

| Mode | M1 Max ms | Spark ms |
|---|---|---|
| ML-DSA-44 | ~0.10 | ~0.08 |
| ML-DSA-65 | ~0.13 | ~0.10 |
| ML-DSA-87 | ~0.17 | ~0.13 |

At 12k gas/ms (geth's published gas-per-millisecond yardstick), that

is ~1.6-2.0k gas of CPU time, well within the conservative 50k Pulsar

base.

Canonical Pulsar-kind input length is 5303 bytes (1 kind + 1 mode +

4 sigLen + 3309 sig + 4 pkLen + 1952 pk + 32 hash). Total gas at this

length: 50000 + 5303 * 10 = 103,030 gas. At a 12M C-Chain block gas

limit, that is ~117 Pulsar-kind P3Q verifies per block before the

rest of the block budget is exhausted. Corona-kind verifies cost ~5M

gas each (2 per 12M block); Magnetar-kind verifies cost ~30k each

(~400 per 12M block, bounded by CPU verify time in practice).

Slot history: STARK-FRI rename

The 0x012205 slot was previously occupied by a Plonky3-fork STARK / FRI

verifier dispatch that had been misnamed "P3Q" — its package p3q

contained the line "P3Q strict-PQ STARK verifier" and the magic header

"P3Q1" still threads through its proof artifacts (EasyCrypt, Lean,

Jasmin, dudect). Per HANZO-CRYPTO-SUITE §5.2 and ROADMAP-CRYPTO-STACK

§B.11, P3Q canonically means "Post-Quantum Pulsar Proof" — the on-chain

Pulsar verifier. The STARK / FRI dispatch was therefore moved to its

own precompile slot:

The geth EVM wiring (lux/geth/core/vm/lux_precompiles.go), chain

import shims (lux/chains/evm/main.go, lux/evm/precompile/registry/registry.go),

and dudect CT harness (lux/crypto/p3q/ct/dudect/verify_ct.go) were

updated in lockstep to the renamed identifiers.

Wire schemas

Rollup transaction envelopes are ZAP frames in the **0xE8..0xEF

rollup schema range** per LP-300 master schema registry. (The original

draft of this LP claimed 0xE0..0xEF, which collided with LP-211

cross-shard atomic at 0xE2..0xE7; per the canonical Option-B map in

LP-300, the consensus-extension band 0xE0..0xEF is partitioned as

0xE0..0xE1 LP-208 DAG mempool, 0xE2..0xE7 LP-211 cross-shard,

0xE8..0xEF LP-218 rollup-VM.)

| Schema ID | Envelope | Kind | Purpose |
|---|---|---|---|
| 0xE8 | RollupBatchTx | 0x40 | Sequencer commit of a batch of N txs to Z-Chain |
| 0xE9 | RollupChallengeTx | 0x41 | Fraud-proof challenge for optimistic rollups (omitted by ZK rollups) |
| 0xEA | RollupExitTx | 0x42 | User exit from rollup to parent L1 with state-membership proof |
| 0xEB | RollupGenesisTx | 0x43 | Rollup-creation tx — submitted once at rollup deploy time |
| 0xEC..0xEF | reserved | — | Reserved for future rollup-VM envelopes (cross-rollup atomic commit, sequencer rotation, etc.) |

Schema-ID allocation map

| Range | Owner | LP |
|---|---|---|
| 0xC0..0xCB | chains-VM wire (12 VMs) | LP-186 |
| 0xD0..0xDF | ZAP P2P transport | LP-201 |
| 0xE0..0xE1 | DAG mempool | LP-208 |
| 0xE2..0xE7 | cross-shard atomic | LP-211 |
| 0xE8..0xEF | rollup-VM envelopes | LP-218 (this LP) |
| 0xF0..0xF2 | light client wire | LP-214 |
| 0xF3..0xFF | reserved future | — |

LP-300 master schema registry is normative; this table is informative.

Kind bytes 0x40..0x43 live in a separate namespace per LP-200

§"Frame structure" and do not need a master registry today.

RollupBatchTx (0xE8, Kind 0x40)

Fixed section (offset table):

| Offset | Field | Size | Description |
|---|---|---|---|
| 0 | Kind | 1 | 0x40 |
| 1 | Version | 1 | 0x01 |
| 2 | SequencerID | 20 | Sequencer node ID (or threshold-group ID for t-of-n) |
| 22 | PrevRoot | 32 | State root before this batch |
| 54 | NewRoot | 32 | State root after this batch |
| 86 | BatchHash | 32 | sha256(concat(tx_i bytes)) over the N tx ZAP frames |
| 118 | BatchNum | 8 | Monotonic batch counter per rollup |
| 126 | TxCount | 4 | N — number of txs in batch |
| 130 | PulsarSigOff | 4 | Offset to Pulsar threshold sig in tail |
| 134 | GroupPubKeyOff | 4 | Offset to Pulsar group public key in tail |
| 138 | ZKProofOff | 4 | Offset to STARK / Groth16 proof (zero if optimistic rollup) |
| 142 | TxFramesOff | 4 | Offset to packed list of tx ZAP frames |

Variable-length tail: Pulsar sig, group pubkey, optional ZK proof,

packed tx frames. The tx frames are the exact same ZAP buffers the

sequencer received from clients — no re-encode, no re-frame.

RollupChallengeTx (0xE9, Kind 0x41)

Submitted by any peer to challenge an optimistic rollup batch during

its challenge window. Fields:

| Offset | Field | Size | Description |
|---|---|---|---|
| 0 | Kind | 1 | 0x41 |
| 1 | Version | 1 | 0x01 |
| 2 | ChallengedBatchID | 32 | sha256 of the challenged RollupBatchTx |
| 34 | ChallengerID | 20 | Challenger node ID |
| 54 | FraudWitnessOff | 4 | Offset to fraud witness in tail |
| 58 | BondAmount | 8 | Challenger bond (slashable if challenge invalid) |

Tail: fraud witness — typically a single tx ZAP frame plus a state

witness showing the sequencer applied the tx incorrectly. The Z-Chain

re-executes the tx witness; if the sequencer's claimed new root

disagrees, the batch is rejected and the sequencer is slashed. Omitted

entirely from ZK rollups (no challenge window).

RollupExitTx (0xEA, Kind 0x42)

A user exits a rollup back to the parent L1 by proving inclusion of a

withdrawal record in a finalized batch.

| Offset | Field | Size | Description |
|---|---|---|---|
| 0 | Kind | 1 | 0x42 |
| 1 | Version | 1 | 0x01 |
| 2 | UserAddress | 20 | Parent L1 address receiving the exit |
| 22 | BatchID | 32 | sha256 of the RollupBatchTx the exit is anchored to |
| 54 | ExitAmount | 32 | uint256 — amount being exited |
| 86 | TokenID | 20 | Token contract address on rollup |
| 106 | MerkleProofOff | 4 | Offset to state-membership Merkle proof in tail |

Tail: Merkle proof from NewRoot of BatchID down to the withdrawal

leaf. Z-Chain verifies the proof against the recorded root.

RollupGenesisTx (0xEB, Kind 0x43)

Submitted once at rollup-deploy time. Establishes the rollup's

parameters on Z-Chain.

| Offset | Field | Size | Description |
|---|---|---|---|
| 0 | Kind | 1 | 0x43 |
| 1 | Version | 1 | 0x01 |
| 2 | RollupID | 20 | Globally-unique rollup ID |
| 22 | DeployerAddress | 20 | Parent L1 address deploying the rollup |
| 42 | RollupMode | 1 | 0x00 optimistic, 0x01 ZK |
| 43 | CertPolicyMode | 1 | LP-217 CertPolicy.Mode required on the parent L1 (see §"Inherited cert mode" below) |
| 44 | ChallengeWindowBlocks | 8 | Optimistic challenge window (zero for ZK) |
| 52 | InitialRootOff | 4 | Offset to initial state root |
| 56 | SequencerPubKeyOff | 4 | Offset to sequencer Pulsar group pubkey |
| 60 | ConfigOff | 4 | Offset to rollup-specific config blob |

Tail: initial root, sequencer Pulsar group public key, rollup config

(execution engine, fee config, etc.).

Rollup VM execution

The rollup-VM execution stack reuses the LP-200 ZAP buffer guarantee:

no re-encoding at any stage. The sequencer's hot path:


NIC RAM ─GPUDirect RDMA─▶ GPU memory (LP-203 UMA)
                                  │
                                  ▼
                          Mempool (ZAP frames)
                                  │
                                  ▼
                          Sequencer batch close
                                  │
                                  ▼
                          State diff + Merkle root (GPU kernel)
                                  │
                                  ▼
                          Pulsar threshold sign (GPU kernel)
                                  │
                                  ▼
                          RollupBatchTx (ZAP frame, same buffer)
                                  │
                                  ▼
                          QUIC submit to Z-Chain (LP-201)

Every stage operates on the same UMA-backed ZAP buffer. No Marshal,

no Unmarshal, no codec, no copy. This is the operational dividend

of the LP-200 stack guarantee — it applies at Tier-3 identically to

how it applies at Tier-1.

The sequencer may be:

Two rollup modes

| Mode | State integrity model | RollupChallengeTx | ZK proof in RollupBatchTx | Withdrawal latency | Use case |
|---|---|---|---|---|---|
| Optimistic | Sequencer asserts; anyone may challenge during window | Required mechanism | Absent | ≥ challenge window (typically 7 days) | High-throughput general-purpose rollups; gaming; social |
| ZK | Each batch carries STARK / Groth16 proof of correct execution | Absent (no challenge needed) | Required | Immediate (~one parent L1 block after Pulsar commit) | Custody-grade rollups; payment finality; cross-rollup composition |

Both modes share the same envelopes and the same P3Q precompile

verification path. The difference is whether the integrity of the

state transition is established by (a) sequencer Pulsar-signed

attestation plus optimistic-challenge window, or (b) sequencer

Pulsar-signed attestation plus STARK / Groth16 proof of correct

execution. The Pulsar-sig + P3Q path is identical; the integrity

witness differs.

Rollup deployer picks mode at genesis (RollupMode field in

RollupGenesisTx). No mode-switching after deploy.

Inheritance from parent L1

Per LP-204 "Tier-3 rollups have NO own validator set", a rollup's

security model IS the parent L1's security model. Concrete

consequences:

| Property | Source |
|---|---|
| Block finality cryptographic strength | Parent L1 QuasarCert under its LP-217 cert mode |
| Sequencer payment gas token | Parent L1 native gas (Z-Chain settlement gas) |
| Rollup state commit | Embedded in a parent L1 block (Z-Chain block on Lux primary) |
| Tier degradation behavior | Parent L1 LP-202 tier degradation applies — rollup users see the parent L1's degraded cert tier with no separate degradation |
| Liveness | Bounded by parent L1 liveness — if Z-Chain stops, rollup stops; if rollup sequencer fails, parent L1 continues, rollup pauses until sequencer restarts or rotates |
| Slashing | Sequencer slashable on RollupChallengeTx (optimistic) or on invalid Pulsar sig; slashing settles on the parent L1 |

Rollups DO NOT add a new validator set. There is no rollup-side stake,

no rollup-side leader election, no rollup-side consensus quorum. The

sequencer is a sequencer, not a validator — its job is ordering and

batching, not finality. Finality comes from the parent L1.

Inherited cert mode

Each rollup pins one field of the LP-217 CertPolicy struct at

genesis: CertPolicy.Mode (historically named CertModeFloor;

both names refer to the same value). This is the minimum cert mode

the parent L1 must be operating at for the rollup batch to be

considered final.

The other three CertPolicy fields (Variant, TimeoutMs,

Fallback) are inherited from the parent L1 by reference. Tier-3

rollups are the only tier where a partial-CertPolicy declaration

is valid (see LP-204 §"Tier-3 — L3 rollups on Z-Chain"). A rollup

does not own TimeoutMs because the parent L1 owns the cert

timeline; the rollup observes the parent L1's tier-degradation

behavior.

| (Mode, Variant) on parent L1 | Wire name | Required parent-L1 legs |
|---|---|---|
| (PQ-off, hybrid) | PQ-off | BLS |
| (PQ-fast, hybrid) | PQ-fast | BLS + Pulsar |
| (PQ-strict, hybrid) | PQ-strict | BLS + Pulsar + Corona |
| (PQ-heavy, hybrid) | PQ-heavy | BLS + Pulsar + Corona + Magnetar |
| (PQ-fast, strict) | strict-PQ-fast | Pulsar |
| (PQ-strict, strict) | strict-PQ-strict | Pulsar + Corona |
| (PQ-heavy, strict) | strict-PQ-heavy | Pulsar + Corona + Magnetar |

A rollup cannot ENFORCE a stronger CertPolicy.Mode than its parent

L1 operates at. If parent L1 is at PQ-fast, the rollup cannot

synthesize PQ-strict finality — it would have to wait for legs the

parent L1 never produces. A rollup that requires PQ-strict MUST

deploy on a parent L1 operating at PQ-strict or higher. Genesis

validation refuses such a configuration before any rollup batch is

processed.

The rollup MAY pin its Mode lower than the parent L1's Mode and

finalize earlier in the LP-202 tier-degradation timeline. A rollup

with CertPolicy.Mode = PQ-fast running on a PQ-strict parent L1

finalizes at the parent L1's PQ-fast preliminary tier (~5 ms), not

the PQ-strict final tier (~15 ms).

Cross-rollup atomicity

Two rollups deployed on the same Z-Chain instance can commit

atomically: both RollupBatchTxes land in the same Z-Chain block and

share the same QuasarCert. Either both finalize or neither does. This

is automatic — no additional protocol is needed.

Cross-L1 atomicity (rollup-on-Lux ↔ rollup-on-Hanzo-L1) requires the

bridgevm path defined in LP-204 §"Tier-1 ↔ Tier-1 bridge" and is out

of scope for this LP. A future LP-219 will specify rollup-to-rollup

cross-L1 atomic composition.

Performance characteristics

| Metric | Value | Source |
|---|---|---|
| P3Q precompile verify, single sig | ~50 μs on Blackwell sm_120 | LP-203 Pulsar verify bench, Blackwell column |
| P3Q precompile verify, batched (N=64) | ~5 ms aggregate | LP-203 Pulsar aggregate row |
| Rollup batch finalization end-to-end (PQ-fast parent) | ~5 ms | Parent-L1 PQ-fast preliminary + P3Q verify |
| Rollup batch finalization end-to-end (PQ-strict parent) | ~15 ms | Parent-L1 PQ-strict final + P3Q verify |
| Sequencer throughput (single sequencer node) | 100k–1M tx/sec | NIC-bound at LP-201 wire speed; not Z-Chain-bound |
| Aggregate throughput, M rollups on one Z-Chain | 10M+ tx/sec | M sequencers compose; Z-Chain only verifies M Pulsar sigs per block |
| Gas cost per rollup batch on Z-Chain | ~50,000 gas (P3Q) + ~21,000 base | One P3Q verify per RollupBatchTx |

Throughput scales horizontally in M (rollups per Z-Chain). Z-Chain's

verify capacity is the bottleneck only at M >> 100 — at M=100, Z-Chain

verifies 100 P3Q sigs per block (~5 ms total verify, well within

block budget).

Architectural decisions

Unified PQ verifier family (one slot, kind-byte dispatch)

P3Q at slot 0x012205 is a PQ verifier family, not a single

primitive. The 1-byte kind discriminator at the head of calldata

selects the underlying P3QVerifier impl: 0x01 Pulsar

(Module-LWE / FIPS-204 ML-DSA), 0x02 Corona (Module-LWE threshold),

0x03 Magnetar (FIPS-205 SLH-DSA threshold). LP-220 specifies the

Corona and Magnetar uses of this slot; the framework type

P3QVerifier and its registration model live in LP-220

§"Architectural decision — common verifier framework".

Rationale: one slot is one audit surface, one ABI shape, one dispatch

path, one operator-known address — the canonical "one and only one

way to do everything" applied to PQ-family verification. Adding a

fourth PQ family (a code-based threshold sig, say) is one new

P3QVerifier impl and one new kind byte, not a new precompile

slot. The earlier LP-218 design ("Pulsar-only verifier; LP-220 to

add separate precompiles") proliferated audit surfaces and ABI shapes

for one family — decomplected on 2026-06-03 into a single slot with

kind-byte dispatch.

Rollups MAY run at PQ-off; SHOULD NOT for value-bearing state

The protocol allows CertPolicy.Mode = PQ-off (BLS only). This is the

right choice for ephemeral or non-financial rollups: gaming session

state, social-graph mutations, real-time bidding, telemetry.

For value-bearing state — money, securities, identity, custody —

the recommended floor is PQ-strict (BLS + Corona + Pulsar). This

matches the floor recommended by LP-217 for "custody, treasury,

regulated securities, store-of-value". A rollup pinning a lower floor

for value-bearing state is making a posture choice; the protocol does

not prevent it but also does not endorse it.

This LP does not introduce a protocol-level requirement that rollups

hit PQ-strict. The parent L1's cert mode is the security ceiling;

the rollup pins its floor anywhere up to that ceiling. Codifying a

"must be PQ-strict for tokens > $X" rule belongs in operator-policy,

not in LP-218.

Sequencer-side Pulsar signing is REQUIRED; ZK proof is OPTIONAL

Every RollupBatchTx MUST carry a valid Pulsar threshold signature

verified by P3Q. There is no path where a rollup batch skips P3Q

verification.

The ZK proof field (ZKProofOff) is optional. If present, Z-Chain

verifies it inline (Groth16 verify ≈ 200k gas, STARK verify ≈ 500k gas

on a generic verifier). If absent, the batch is optimistic and subject

to challenge.

Rationale: the Pulsar signature is the authenticity witness (the

sequencer attests to the batch). The ZK proof is the correctness

witness (the batch was applied correctly). Authenticity is always

required; correctness can be optimistic.

No rollup-side validator set

Specified by LP-204 and reaffirmed here. A rollup is a sequencer plus

a state machine, not a chain with its own consensus. Anyone who wants

their own validator set deploys an L1, not a rollup.

Honest cost comparison

| Verifier | Gas cost | Verify time on Blackwell | Verify time on CPU | Notes |
|---|---|---|---|---|
| ecrecover (secp256k1) | 3,000 | ~3 μs | ~10 μs | Classical, broken by Shor's |
| BLS pairing verify | 5,000 | ~10 μs | ~6 ms | Classical, broken by Shor's |
| P3Q (Pulsar / ML-DSA-65) | 50,000 | ~50 μs | ~80 μs | PQ-secure |
| Re-running rollup consensus | N/A | ~5–50 ms (cert mode dep) | N/A | What we are NOT doing |

P3Q is more expensive than BLS verify because PQ verification is

inherently heavier — lattice operations dominate elliptic-curve

operations on every CPU and most GPUs. The 10× factor is honest.

P3Q is dramatically cheaper than re-running consensus to attest to the

rollup batch. The rollup pays 50k gas per batch instead of running an

entire validator set per batch. This is the economic case for the

pattern.

Activation marker


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

The P3Q precompile is callable at slot 0x012205 from the genesis

block of the new final Lux network. Rollup deployments via

RollupGenesisTx are accepted by Z-Chain from height 0.

Cross-references

Future work