Lux Proposals
← All proposals
LP-0182Final

Status

The Lux consensus wire is ZAP. The Go struct IS the wire — there is

no codec, no marshal step, no encode or decode. A QuasarCert is a

buffer; accessors are offset reads on that buffer; Bytes() returns

the buffer.

Activated at the genesis of the new final Lux network:

2025-12-25 16:20 Pacific (unix 1766708400).

There is no other wire on this network. No legacy. No backwards

compatibility. The pre-Quasar Edition Lux network (2020–2025) is a

separate network and is out of scope.

The struct IS the wire


type QuasarCert struct {
    msg *zap.Message       // the wire buffer itself
    obj zap.Object         // typed window into msg
}

func (c QuasarCert) BLS() []byte    { return c.obj.BytesRef(OffsetBLS) }       // offset read
func (c QuasarCert) Epoch() uint64  { return c.obj.Uint64(OffsetEpoch) }       // offset read
func (c QuasarCert) Bytes() []byte  { return c.msg.Bytes() }                   // return buffer

There is no Marshal. There is no Unmarshal. There is no Encode.

There is no Decode. There is no Serialize. Parse(b) wraps b in

a typed accessor — it does not copy and does not decode. Build(...)

writes fields into a buffer at known offsets and the buffer is the

wire bytes — there is no encoding pass over it.

This is the Cap'n-Proto / FlatBuffers invariant: **wire format is the

in-memory representation**.

Decomplected layers

Three layers, each one job. They compose. They do not braid.

| Layer | Purpose | Implementation |
|---|---|---|
| Transcript | bytes that get signed | TupleHash256 / cSHAKE256 (SP 800-185) |
| Wire | bytes on network and disk | ZAP buffer = the struct |
| Identity | stable consensus IDs | sha256.Sum256 over canonical bytes |

The wire layer does not know about the transcript; it offers

Bytes(). The transcript layer accepts byte slices and length-prefixes

them into a TupleHash; it does not know about ZAP. The identity layer

hashes Bytes(); it does not know about either.

Specification

Wire schemas

~/work/lux/consensus/pkg/wire/zap/schemas.go is the single registry

of schema IDs. Each schema is a fixed offset map for one consensus

type. The struct embedding a *zap.Message IS the type's value.

| ID | Type | File |
|---:|---|---|
| 0x01 | QuasarCert | pkg/wire/zap/quasar_cert.go |
| 0x02 | QBlock | pkg/wire/zap/qblock.go |
| 0x03 | WitnessProof | pkg/wire/zap/witness.go |
| 0x04 | MagnetarAggregateCert | pkg/wire/zap/magnetar_aggregate.go |
| 0x05 | PolarisLegs | pkg/wire/zap/polaris_legs.go |
| 0x06 | QuasarSig | pkg/wire/zap/quasar_sig.go |
| 0x07 | EpochBundle | pkg/wire/zap/epoch_bundle.go |
| 0x08 | PrismCut | pkg/wire/zap/prism_cut.go |
| 0x09 | StakeWeightedCut | pkg/wire/zap/stake_weighted_cut.go |
| 0x0A | TxAuthEnvelope | pkg/wire/zap/tx_auth_envelope.go |
| 0x0B | PQPermit | pkg/wire/zap/pq_permit.go |
| 0x0C | DAGVertex | pkg/wire/zap/dag_vertex.go |
| 0x0D | PolicyCert | pkg/wire/zap/policy_cert.go |

Transcript

Unchanged. protocol/quasar/round_digest.go::ComputeRoundDigest and

protocol/quasar/qblock.go::TranscriptHash keep their existing field

layouts. Inputs are now Bytes() from ZAP structs instead of

hand-rolled binary, but byte-for-byte identical for identical fields

because SP 800-185 length-prefix framing is what fixes the transcript

layout — not the upstream byte producer.

Customization tags QUASAR-ROUND-DIGEST and QUASAR-Q-BLOCK-V1 are

cryptographic constants and remain byte-identical.

Identity

Unchanged. sha256.Sum256(x.Bytes()) for any consensus type x.

Out of scope

Wires owned by other repositories:

Consensus embeds the opaque byte strings these produce. This LP does

not redefine them.

External RPC surface:

Rationale

One wire. Decomplection: the wire layer does exactly one job

(present typed accessors over a byte buffer). Multiple wires would

braid two unrelated concerns (which wire to read) with one job

(read the bytes). One wire is single-purpose.

ZAP because the struct IS the wire. Every other option — protobuf,

RLP, msgpack, hand-rolled binary — is a codec: serialize on write,

deserialize on read. ZAP eliminates the step. The buffer is the value.

x.Bytes() returns it. sha256(x.Bytes()) requires no re-encoding.

That is the property no other option offers.

Activation at the genesis of the new final Lux network. The new

generation came online 2026-05-31 (devnet) and 2026-06-01 (testnet,

mainnet). Activation timestamp predates every block on the new

generation. Every block on the new final Lux network is a ZAP block

by construction. The v1.10.x luxd binary running today not yet

implementing ZAP wire is a binary defect to close in the next

release — not a chain-history fact to preserve.

No backwards compatibility

None.

The new final Lux network has one wire, present in the binary from

the release that ships this LP. A node presenting non-ZAP bytes for

a consensus envelope is not a member of the network by definition.

The pre-Quasar Edition Lux network (2020–2025) is a separate network

with its own wire. Not migrated. Not read. Not referenced.

No replay rules

None. Activation predates every block on the network. There are no

pre-activation blocks to replay under a different wire.

No flags

None. The binary speaks ZAP. Byte input that fails ZAP parse is

rejected. No LUXD_ENABLE_* knob, no dispatch by magic byte, no

runtime selection.

Cross-implementation byte equality

test/e2e/{python,cpp,c,rust}_node.go are ZAP-only. The schema

registry pkg/wire/zap/schemas.go is the single source of truth;

each stub generates accessors from that source.

CI gate: test/e2e/cross_impl_byte_equality_test.go produces N random

consensus envelopes via the Go reference, asks each stub to round-

trip them, and asserts byte equality. Required green for any release

tag.

Implementation

| File | Change |
|---|---|
| pkg/wire/zap/schemas.go | New — schema ID registry |
| pkg/wire/zap/quasar_cert.gopolicy_cert.go (13 files) | New — one per schema |
| protocol/quasar/types.go | Delete QuasarCert.MarshalBinary/UnmarshalBinary |
| protocol/quasar/qblock.go (wire portion) | Delete hand-rolled binary block |
| protocol/quasar/witness.go | Delete hand-rolled binary |
| protocol/quasar/polaris.go | Delete EncodeMagnetarAggregate/DecodeMagnetarAggregate |
| protocol/quasar/epoch.go | Delete hand-rolled bundle binary |
| protocol/quasar/bls.go | Delete hand-rolled buildVoteDigest binary |
| protocol/quasar/grouped_threshold.go | Delete hand-rolled checkpoint binary |
| protocol/auth/tx_envelope.go, permit.go, hash.go | Delete hand-rolled binary |
| protocol/prism/cut.go, stake_weighted_cut.go | Delete hand-rolled binary |
| protocol/photon/emitter.go | Delete hand-rolled binary |
| engine/dag/vertex.go, engine/dag/state/state.go | Re-point at schema 0x0C |
| pkg/wire/policies.go (QuasarCert wrap) | Re-point at schema 0x0D |
| utils/codec/codec.go | Delete — orphan JSON codec, zero callers |
| protocol/quasar/round_digest.go | Unchanged |
| protocol/quasar/qblock.go::TranscriptHash | Unchanged |
| pkg/wire/wire.go | Unchanged — external JSON-RPC surface |
| pkg/wire/candidate.go, credentials.go | Unchanged — canonical-input hashes |

Preserved files belong to other layers (transcript, external RPC,

canonical-input identity). Deleted files were the hand-rolled binary

wire layer, now exactly one wire — ZAP.

Test cases


TestQuasarCertBuildParseFieldEquality   build → Bytes() → Parse → fields equal
TestQuasarCertBytesIsUnderlyingBuffer   Bytes() returns msg buffer, no copy
TestQuasarCertParseRefusesNonZAP        non-ZAP bytes reject at Parse
TestComputeRoundDigest_Stability         transcript bytes byte-identical to fixture
TestTranscriptHash_Stability             q-block transcript byte-identical to fixture
TestCrossImpl_ByteEquality_AllSurfaces   python/cpp/c/rust ≡ Go
TestNoLegacyCodecInBinary                grep build symbols — no Marshal*, no Unmarshal*,
                                          no linearcodec, no encoding/binary in consensus
                                          protocol package

Lives in ~/work/lux/consensus/pkg/wire/zap/ and

~/work/lux/consensus/test/e2e/.

Reference implementation

~/work/lux/consensus/pkg/wire/zap/ — lands in the luxd release

carrying this LP.

Cross-references

Activation marker

Every future Lux network-upgrade LP that changes the consensus wire

layer on the new final Lux network MUST use the same activation

marker as this LP:


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

Set once. Does not move.