Documentation
¶
Overview ¶
Package zap (v1) is deprecated for new schema authoring.
New schemas MUST be authored via codegen (~/work/lux/zap/v2/codegen) which emits v1-equivalent fast paths from a declarative .zap source.
Consumer dispatch for ad-hoc / dynamic schemas goes through the v2 generic API (github.com/luxfi/zap/v2).
The v1 hand-rolled code in this package remains for in-flight migration of legacy callers (luxfi/node/vms/platformvm/txs/zap_native, parts of luxfi/consensus/protocol/quasar, etc.); no new v1 schemas are accepted.
The wire format is unchanged across v1 and v2 — this is a code-level decomplection of the authoring surface only. Existing v1 buffers remain parseable by both v1 and v2.
See ~/work/lux/zap/v2/README.md for the canonical authoring path.
Package zap implements the Zero-copy Application Protocol (ZAP) for Lux.
ZAP is a binary serialization format designed for high-performance inter-process and network communication. Like Cap'n Proto and FlatBuffers, ZAP enables zero-copy reads - data can be accessed directly from the underlying byte buffer without parsing or allocation.
Transport security: set NodeConfig.TLS to a *tls.Config to wrap all TCP connections with TLS. This supports PQ-TLS 1.3 when the Go runtime and configured cipher suites provide post-quantum key exchange (e.g. X25519Kyber768). When TLS is nil (the default), connections are plaintext.
Wire Format:
┌─────────────────────────────────────────────────┐ │ Header (16 bytes) │ │ ├─ Magic (4 bytes): "ZAP\x00" │ │ ├─ Version (2 bytes): 1 (legacy) or 2 (current)│ │ ├─ Flags (2 bytes): compression, etc. │ │ ├─ Root Offset (4 bytes): offset to root │ │ └─ Size (4 bytes): total message size │ ├─────────────────────────────────────────────────┤ │ Data Segment (variable) │ │ └─ Structs, lists, text, bytes... │ └─────────────────────────────────────────────────┘
All multi-byte integers are little-endian. Offsets are relative to the position of the offset field itself.
Index ¶
- Constants
- Variables
- func AsPQConn(c net.Conn) (*pqConn, error)
- func DecodeNodeIDHandshake(data []byte) (string, bool)
- func EncodeNodeIDHandshake(nodeID string) []byte
- func ParseHeader(data []byte) ([]byte, int, error)
- func RegisterTransport(t Transport, f TransportFactory)
- func TLSCertFingerprintFromBytes(certDER []byte) [32]byte
- func TranscriptHash(ctx *AttestationContext) [48]byte
- func TypeSize(t Type) int
- func UnwrapCorrelated(data []byte) (reqID uint32, flag uint32, body []byte, ok bool)
- func VerifyRegistration(reg *VMRegistration, chainAuthority *mldsa.PublicKey, mode RegistrationMode) (*mldsa.PublicKey, error)
- func WrapCorrelated(reqID uint32, flag uint32, body []byte) []byte
- func WrapPQ(conn net.Conn, sess *handshake.Session) net.Conn
- type Address
- type Attestation
- type AttestationContext
- type Bloom
- type Builder
- func (b *Builder) Finish() []byte
- func (b *Builder) FinishWithFlags(flags uint16) []byte
- func (b *Builder) Reset()
- func (b *Builder) StartList(elemSize int) *ListBuilder
- func (b *Builder) StartObject(dataSize int) *ObjectBuilder
- func (b *Builder) WriteBytes(data []byte) int
- func (b *Builder) WriteText(s string) int
- type Conn
- type Enum
- type Field
- type Handler
- type Hash
- type List
- func (l List) Address(i int) Address
- func (l List) Bytes() []byte
- func (l List) Hash(i int) Hash
- func (l List) IsNull() bool
- func (l List) Len() int
- func (l List) Object(i int, elemSize int) Object
- func (l List) Uint8(i int) uint8
- func (l List) Uint32(i int) uint32
- func (l List) Uint64(i int) uint64
- type ListBuilder
- type Message
- type Node
- func (n *Node) Broadcast(ctx context.Context, msg *Message) map[string]error
- func (n *Node) Call(ctx context.Context, peerID string, msg *Message) (*Message, error)
- func (n *Node) ConnectDirect(addr string) error
- func (n *Node) Handle(msgType uint16, handler Handler)
- func (n *Node) NodeID() string
- func (n *Node) Peers() []string
- func (n *Node) Send(ctx context.Context, peerID string, msg *Message) error
- func (n *Node) Start() error
- func (n *Node) Stop()
- type NodeConfig
- type Object
- func (o Object) Address(fieldOffset int) Address
- func (o Object) AddressSlice(fieldOffset int) []byte
- func (o Object) Bool(fieldOffset int) bool
- func (o Object) Bytes(fieldOffset int) []byte
- func (o Object) BytesFixedSlice(fieldOffset, n int) []byte
- func (o Object) Float32(fieldOffset int) float32
- func (o Object) Float64(fieldOffset int) float64
- func (o Object) Hash(fieldOffset int) Hash
- func (o Object) HashSlice(fieldOffset int) []byte
- func (o Object) Int8(fieldOffset int) int8
- func (o Object) Int16(fieldOffset int) int16
- func (o Object) Int32(fieldOffset int) int32
- func (o Object) Int64(fieldOffset int) int64
- func (o Object) IsNull() bool
- func (o Object) List(fieldOffset int) List
- func (o Object) ListStride(fieldOffset int, minStride uint32) List
- func (o Object) Message() *Message
- func (o Object) Object(fieldOffset int) Object
- func (o Object) Offset() int
- func (o Object) Signature(fieldOffset int) Signature
- func (o Object) Text(fieldOffset int) string
- func (o Object) Uint8(fieldOffset int) uint8
- func (o Object) Uint16(fieldOffset int) uint16
- func (o Object) Uint32(fieldOffset int) uint32
- func (o Object) Uint64(fieldOffset int) uint64
- type ObjectBuilder
- func (ob *ObjectBuilder) Finish() int
- func (ob *ObjectBuilder) FinishAsRoot() int
- func (ob *ObjectBuilder) ReserveFixed(dataSize int)
- func (ob *ObjectBuilder) SetAddress(fieldOffset int, addr Address)
- func (ob *ObjectBuilder) SetBool(fieldOffset int, v bool)
- func (ob *ObjectBuilder) SetBytes(fieldOffset int, v []byte)
- func (ob *ObjectBuilder) SetBytesFixed(fieldOffset int, v []byte)
- func (ob *ObjectBuilder) SetFloat32(fieldOffset int, v float32)
- func (ob *ObjectBuilder) SetFloat64(fieldOffset int, v float64)
- func (ob *ObjectBuilder) SetHash(fieldOffset int, h Hash)
- func (ob *ObjectBuilder) SetInt8(fieldOffset int, v int8)
- func (ob *ObjectBuilder) SetInt16(fieldOffset int, v int16)
- func (ob *ObjectBuilder) SetInt32(fieldOffset int, v int32)
- func (ob *ObjectBuilder) SetInt64(fieldOffset int, v int64)
- func (ob *ObjectBuilder) SetList(fieldOffset int, listOffset int, length int)
- func (ob *ObjectBuilder) SetObject(fieldOffset int, objOffset int)
- func (ob *ObjectBuilder) SetSignature(fieldOffset int, sig Signature)
- func (ob *ObjectBuilder) SetText(fieldOffset int, v string)
- func (ob *ObjectBuilder) SetUint8(fieldOffset int, v uint8)
- func (ob *ObjectBuilder) SetUint16(fieldOffset int, v uint16)
- func (ob *ObjectBuilder) SetUint32(fieldOffset int, v uint32)
- func (ob *ObjectBuilder) SetUint64(fieldOffset int, v uint64)
- type RegistrationMode
- type Schema
- type Signature
- type StaticVMRegistry
- type Struct
- type StructBuilder
- func (sb *StructBuilder) Address(name string) *StructBuilder
- func (sb *StructBuilder) Bool(name string) *StructBuilder
- func (sb *StructBuilder) Build() *Struct
- func (sb *StructBuilder) Bytes(name string) *StructBuilder
- func (sb *StructBuilder) Float64(name string) *StructBuilder
- func (sb *StructBuilder) Hash(name string) *StructBuilder
- func (sb *StructBuilder) Int32(name string) *StructBuilder
- func (sb *StructBuilder) Int64(name string) *StructBuilder
- func (sb *StructBuilder) List(name string, elemType Type) *StructBuilder
- func (sb *StructBuilder) Signature(name string) *StructBuilder
- func (sb *StructBuilder) Struct(name string, structName string) *StructBuilder
- func (sb *StructBuilder) Text(name string) *StructBuilder
- func (sb *StructBuilder) Uint32(name string) *StructBuilder
- func (sb *StructBuilder) Uint64(name string) *StructBuilder
- type Transport
- type TransportConn
- type TransportFactory
- type TransportStream
- type TransportStreamer
- type Type
- type VMRegistration
- type VMRegistry
Constants ¶
const ( // AddressSize is the size of an EVM address (20 bytes) AddressSize = 20 // HashSize is the size of a keccak256 hash (32 bytes) HashSize = 32 // SignatureSize is the size of an ECDSA signature (65 bytes: r[32] + s[32] + v[1]) SignatureSize = 65 // BloomSize is the size of a bloom filter (256 bytes) BloomSize = 256 )
const ( FieldReqID = 0 // uint32 - request ID for correlation FieldReqFlag = 4 // uint32 - 1=request, 2=response ReqFlagReq = 1 ReqFlagResp = 2 )
Reserved header fields for request/response correlation These are the first 8 bytes of every Call message
const ( // HeaderSize is the size of the ZAP message header HeaderSize = 16 // Magic bytes identifying a ZAP message Magic = "ZAP\x00" // Version of the ZAP wire format. Two schemas are defined: // // Version1 — legacy v2 platformvm schema (NetworkID at byte 0, no TxKind // discriminator). Accepted at Parse for backward compatibility, // but new builders emit Version2 by default. // // Version2 — v3 platformvm schema (TxKind discriminator at byte 0, all // other fields shifted by +1). This is what every Wrap*Tx in // luxfi/node/vms/platformvm/txs/zap_native expects. // // Version (the bare constant) is the CURRENT wire version emitted by // NewBuilder. It tracks Version2; Version1 is preserved only for legacy // parse and explicit-opt-in builds via NewBuilderV1. // // RED-MEDIUM-1 (LP-023 v3.1 round 2): a v2-shaped BaseTx with NetworkID=11 // has byte 0 == 0x0B == TxKindBaseFull. Wrap*Tx on a v1-header v2-schema // buffer with this collision would PASS the discriminator check and // misinterpret the rest of the buffer. Reject at the schema-version gate // in every Wrap*Tx (callers should require Version2). Version1 uint16 = 1 Version2 uint16 = 2 Version uint16 = Version2 // DefaultPort is the canonical TCP port for ZAP transport across the // Lux ecosystem. Like 80 means HTTP and 443 means HTTPS, 9999 means // ZAP — every ZAP-hosting service binds this port; the DNS name (e.g. // zap.kms.svc, zap.mpc.svc) disambiguates which service is on the // other end. DefaultPort = 9999 // Alignment for data segments Alignment = 8 )
const ( FlagNone uint16 = 0 FlagCompressed uint16 = 1 << 0 FlagEncrypted uint16 = 1 << 1 FlagSigned uint16 = 1 << 2 )
Flags for message header
const MaxPQRecord = 64 * 1024
MaxPQRecord is the largest plaintext payload one Write turns into a single DATA frame. Writes larger than this are chunked. Sized well below the §5 16-MiB hard cap so the AEAD tag + frame envelope always fit.
Variables ¶
var ( ErrInvalidMagic = errors.New("zap: invalid magic bytes") ErrInvalidVersion = errors.New("zap: unsupported version") ErrBufferTooSmall = errors.New("zap: buffer too small") ErrOutOfBounds = errors.New("zap: offset out of bounds") ErrInvalidOffset = errors.New("zap: invalid offset") )
var BlockHeaderSchema = NewStructBuilder("BlockHeader").
Hash("parentHash").
Hash("uncleHash").
Address("coinbase").
Hash("stateRoot").
Hash("transactionsRoot").
Hash("receiptsRoot").
Bytes("logsBloom").
Uint64("difficulty").
Uint64("number").
Uint64("gasLimit").
Uint64("gasUsed").
Uint64("timestamp").
Bytes("extraData").
Hash("mixHash").
Uint64("nonce").
Build()
BlockHeaderSchema defines the schema for an EVM block header.
var ErrNotPQConn = errors.New("zap: not a ZAP-PQ connection")
ErrNotPQConn is returned by AsPQConn when the caller passes a net.Conn that is not a *pqConn (e.g. a legacy TCP conn).
var ErrRegistrationMalformed = errors.New("zap-pq: VMRegistration malformed")
ErrRegistrationMalformed is returned when fields are missing or the wrong length.
ErrTransportUnavailable is returned by NewNode when the requested Transport has no registered factory. For TransportQUIC, this means the quic subpackage was not imported.
var LogSchema = NewStructBuilder("Log"). Address("address"). List("topics", TypeBytes). Bytes("data"). Uint64("blockNumber"). Hash("txHash"). Uint32("txIndex"). Hash("blockHash"). Uint32("logIndex"). Bool("removed"). Build()
LogSchema defines the schema for an EVM log entry.
var TransactionSchema = NewStructBuilder("Transaction").
Hash("hash").
Uint64("nonce").
Address("from").
Address("to").
Bytes("value").
Bytes("data").
Uint64("gas").
Bytes("gasPrice").
Uint64("chainId").
Signature("signature").
Build()
TransactionSchema defines the schema for an EVM transaction.
Functions ¶
func AsPQConn ¶ added in v0.6.1
AsPQConn type-asserts an interface{} into a ZAP-PQ wrapper so callers can fetch PeerID without import cycles.
func DecodeNodeIDHandshake ¶ added in v0.6.1
DecodeNodeIDHandshake reads a NodeID exchange message and returns the peer's nodeID. An empty string (with ok=false) indicates a malformed or out-of-range length field.
func EncodeNodeIDHandshake ¶ added in v0.6.1
EncodeNodeIDHandshake builds the NodeID exchange message. nodeIDs longer than maxNodeIDLen are truncated; the receiver validates length on Decode.
func ParseHeader ¶ added in v0.8.0
ParseHeader validates a ZAP wire frame and returns the (validated data slice, root offset) WITHOUT allocating a *Message. Same checks as Parse — magic, version, size — but the result is two values, not a pointer. Intended for generic wrappers (zapv2) that build their own value-typed accessors and never need a *Message.
Returns (data[:size], rootOff, nil) on success.
The wire validation steps mirror Parse exactly (magic + version + size + bounds). The implementation is intentionally written as one linear sequence (no intermediate function calls) so the inliner folds the whole body into the caller. Combined with the value- typed [zapv2.View], this is what makes the v2 read path match v1's 2 ns hand-rolled per-Read cost — zero function calls, zero heap.
func RegisterTransport ¶ added in v0.6.1
func RegisterTransport(t Transport, f TransportFactory)
RegisterTransport plugs a TransportFactory into the registry. The quic subpackage calls this in its init function.
Registration is idempotent — re-registering the same Transport overwrites — but in practice each Transport has exactly one factory linked into the binary.
func TLSCertFingerprintFromBytes ¶ added in v0.3.1
TLSCertFingerprintFromBytes returns sha256(certBytes) sized for the AttestationContext field. Helper so callers don't need to reach into crypto/sha256 separately.
func TranscriptHash ¶ added in v0.3.1
func TranscriptHash(ctx *AttestationContext) [48]byte
TranscriptHash returns the 48-byte SHAKE256-384 commitment a PQ Attestation signature MUST cover. Domain-separated with the "ZAP-PQ-V1" string so a signature produced for ZAP cannot be replayed on any other ML-DSA-signed transcript (warp envelopes, validator-set commitments, etc.).
SP 800-185 left_encode framing on each field so a malicious transcript field whose first bytes spell another field's payload cannot collide with a legitimate transcript.
func UnwrapCorrelated ¶ added in v0.6.1
UnwrapCorrelated reads the correlation header off `data` and returns (reqID, flag, body, ok). If `data` is shorter than the header or the flag isn't a recognised value, ok is false and the caller should treat the message as uncorrelated.
func VerifyRegistration ¶ added in v0.6.1
func VerifyRegistration( reg *VMRegistration, chainAuthority *mldsa.PublicKey, mode RegistrationMode, ) (*mldsa.PublicKey, error)
VerifyRegistration checks AuthoritySig — and PrevVMSig under ModeRotation — against the chain authority public key. Returns the verified VM public key on success.
Signing context for both signatures is the §6.4 SignCtx ("lux-zap-pq-v1") so the same audited verifier handles them. Payload is `VMID ∥ VMPubKey` so a signature over one (VMID, pubkey) pair cannot be re-used for a different pair.
func WrapCorrelated ¶ added in v0.6.1
WrapCorrelated prepends the Call/response correlation header to `body`. The result is what writeMessage emits onto the wire.
func WrapPQ ¶ added in v0.6.1
WrapPQ wraps an established net.Conn with ZAP-PQ-v1 AEAD framing after a completed handshake. The returned net.Conn implements stream Read/Write on top of the record-oriented Session — one Write becomes one DATA frame (chunked at MaxRecord), reads buffer across frames so callers see the byte stream they expect.
Close closes the Session (which zeros the keys and closes the underlying TCP conn). LocalAddr / RemoteAddr / SetDeadline* delegate to the wrapped conn.
Drop-in usage:
tcp, _ := net.Dial("tcp", "...")
sess, _ := (&handshake.Initiator{Local: id}).Run(tcp)
pq := zap.WrapPQ(tcp, sess)
// use pq as any net.Conn from here on
Types ¶
type Address ¶
type Address [AddressSize]byte
Address is a 20-byte EVM address (zero-copy view).
var ZeroAddress Address
ZeroAddress is the zero address.
func AddressFromHex ¶
AddressFromHex parses an address from hex string.
type Attestation ¶ added in v0.3.1
type Attestation struct {
// PubKey is the peer's strict-PQ public key. The verifier
// confirms membership in the chain's validator set BEFORE
// trusting the signature.
PubKey []byte
// Sig is the signature over TranscriptHash(...).
Sig []byte
}
Attestation is the wire shape a ZAP peer presents after the TLS handshake completes. PubKey + Sig are opaque bytes from zap's perspective; the AttestationVerifier owns the format (FIPS 204 ML-DSA-65 pubkey 1952 bytes, signature 3293 bytes for Liquid; ML-DSA-87 with different byte counts for high-value Zoo chains).
func (*Attestation) HasPQEvidence ¶ added in v0.3.1
func (a *Attestation) HasPQEvidence() bool
HasPQEvidence implements pq.PQEvidencer. A non-nil Attestation with a non-empty Sig counts as evidence — the gate then dispatches to the verifier, which actually checks the signature + validator-set membership.
type AttestationContext ¶ added in v0.3.1
type AttestationContext struct {
// TLSCertFingerprint is sha256 of the peer's TLS certificate
// DER. Binding the attestation to this fingerprint means a
// stolen-but-orthogonal TLS cert can't be paired with a
// different PQ identity to MITM the channel.
TLSCertFingerprint [32]byte
// ChainID is the 32-byte chain identifier this connection is
// scoped to. Different chains produce different transcripts,
// so an attestation harvested on chain A is useless on chain B.
ChainID [32]byte
// PeerMLKEMPub is the peer's ML-KEM-768 public key (1184
// bytes). Including it in the transcript binds the
// attestation to the same KEM key the TLS handshake used
// (when X25519MLKEM768 hybrid was negotiated). On classical
// TLS this is empty and the field doesn't contribute.
PeerMLKEMPub []byte
// Timestamp (unix seconds) of the connection. Verifier checks
// |now - timestamp| < some window (e.g. 60s) to refuse
// replays of old captured attestations.
Timestamp uint64
// Nonce is per-session entropy from the verifier — 32 bytes.
// Refuses an attacker who records one valid attestation from
// replaying it on a future session.
Nonce [32]byte
}
AttestationContext bundles the inputs a verifier needs to rebuild the transcript hash. Same inputs on both peers; the PQ signature anchors the binding.
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder constructs ZAP messages.
func NewBuilder ¶
NewBuilder creates a new builder with the given initial capacity. The resulting message is emitted with Version2 in the wire header (the current default). Use NewBuilderV1 only for legacy v1 emitters; new code should always use NewBuilder.
func NewBuilderV1 ¶ added in v0.7.1
NewBuilderV1 creates a new builder that emits Version1 messages. This is kept only for explicit-legacy-emitter call sites (none in tree as of LP-023 v3.1 round 2). New code should call NewBuilder.
func (*Builder) FinishWithFlags ¶
FinishWithFlags finalizes with specific flags.
func (*Builder) StartList ¶
func (b *Builder) StartList(elemSize int) *ListBuilder
StartList starts building a list.
func (*Builder) StartObject ¶
func (b *Builder) StartObject(dataSize int) *ObjectBuilder
StartObject starts building an object with the given data size.
func (*Builder) WriteBytes ¶
WriteBytes writes raw bytes and returns the offset.
type Conn ¶
Conn is a ZAP connection to a peer.
type Enum ¶
type Enum struct {
Name string
Type Type // Underlying type (Uint8, Uint16, etc.)
Values map[string]uint64
}
Enum describes a ZAP enum.
type Field ¶
type Field struct {
Name string
Type Type
Offset int // Byte offset within struct
ListElem Type // Element type if Type == TypeList
StructName string // Struct name if Type == TypeStruct
Default any // Default value
}
Field describes a struct field.
type Hash ¶
Hash is a 32-byte hash (zero-copy view).
var ZeroHash Hash
ZeroHash is the zero hash.
func HashFromHex ¶
HashFromHex parses a hash from hex string.
type List ¶
type List struct {
// contains filtered or unexported fields
}
List is a zero-copy view into a ZAP list.
func (List) Len ¶
Len returns the list element count as encoded on the wire.
SAFETY: callers MUST NOT pre-allocate via make([]T, l.Len()) without an independent bound. The wire encoding only constrains length to len(buffer), so a 64KB mempool tx can carry Len()=65535 — large enough to OOM if a consumer naively pre-allocates. Always iterate List.At(i) with i < Len() AND validate each element's invariants before trusting the count.
For tighter per-stride bounds at the wire layer, use Object.ListStride (introduced in v0.7.2): it rejects length*minStride > len(buffer) up front. This Len() value is the wire-encoded count irrespective of which accessor produced the List — Object.List or Object.ListStride.
type ListBuilder ¶
type ListBuilder struct {
// contains filtered or unexported fields
}
ListBuilder builds a ZAP list.
func (*ListBuilder) AddBytes ¶
func (lb *ListBuilder) AddBytes(data []byte)
AddBytes adds raw bytes (for byte lists).
func (*ListBuilder) AddUint8 ¶
func (lb *ListBuilder) AddUint8(v uint8)
AddUint8 adds a uint8 element.
func (*ListBuilder) AddUint32 ¶
func (lb *ListBuilder) AddUint32(v uint32)
AddUint32 adds a uint32 element.
func (*ListBuilder) AddUint64 ¶
func (lb *ListBuilder) AddUint64(v uint64)
AddUint64 adds a uint64 element.
func (*ListBuilder) Finish ¶
func (lb *ListBuilder) Finish() (offset int, length int)
Finish returns the list offset and length.
type Message ¶
type Message struct {
// contains filtered or unexported fields
}
Message is a ZAP message that can be read zero-copy.
When the message's backing storage was sourced from the pooled read buffer (see bufpool.go), refs is non-nil and Release returns the slab to its pool. For messages built via Builder.Finish() / Parse() of caller-owned bytes, refs is nil and Release is a no-op — those buffers are GC-managed as before.
func Parse ¶
Parse parses a ZAP message from bytes without copying.
Accepts both Version1 and Version2 wire headers (forward-compatible read). Callers that require Version2 semantics (e.g. v3 platformvm schema) must gate on Message.Version() after Parse.
RED-V18 (LP-023 v3.1 round 2): the declared size field must be at least HeaderSize. A buffer with size=0 used to pass Parse and then panic on subsequent Root()/Flags() reads against an empty slice. Now rejected at the wire boundary.
func WrapBuffer ¶ added in v0.8.0
WrapBuffer constructs a *Message over an already-built ZAP buffer WITHOUT re-running Parse's validation checks (magic, version, size bounds). Use ONLY for buffers you just emitted via Builder — they are valid by construction, and skipping the recheck is a 14ns hot-path saving per Build call.
External / untrusted bytes MUST go through Parse, never WrapBuffer.
func (*Message) Release ¶ added in v0.8.0
func (m *Message) Release()
Release returns this message's backing slab to the read-buffer pool when the message was sourced via the pooled read path. Safe to call on any *Message — when the buffer is GC-managed (Builder, Parse of caller bytes) Release is a no-op.
After Release the underlying bytes MAY be overwritten by the next pool consumer. Callers must not use Bytes() / Root() / accessors on a released message.
Release on a nil receiver is a no-op so callers can defer it without nil-checking.
func (*Message) Retain ¶ added in v0.8.0
func (m *Message) Retain()
Retain increments the reference count on this message's backing slab, allowing the caller to hand the message off to a goroutine that will Release it later. No-op when refs is nil.
Pair every Retain with exactly one Release. The dispatch loop calls Retain before pushing a response *Message onto a Call's response channel; the Call goroutine then Releases when it consumes the response.
func (*Message) RootObjectAt ¶ added in v0.8.0
RootObjectAt returns a zap.Object anchored at absolute offset `off` within this message. Used by zapv2.Build to avoid a redundant header-read after [Builder.FinishAsRoot] already returned the root position.
type Node ¶
type Node struct {
// contains filtered or unexported fields
}
Node is a ZAP node that combines mDNS discovery with zero-copy RPC.
func (*Node) ConnectDirect ¶
ConnectDirect connects directly to a peer at the given address (bypasses mDNS).
type NodeConfig ¶
type NodeConfig struct {
NodeID string
ServiceType string // e.g., "_luxd._tcp", "_fhed._tcp"
Port int
Metadata map[string]string
Logger *slog.Logger
NoDiscovery bool // Disable mDNS discovery (use ConnectDirect only)
TLS *tls.Config // optional PQ-TLS 1.3; nil = plaintext
// Transport selects the network transport: TransportTCP (default,
// preserves back-compat) or TransportQUIC. TransportQUIC requires
// `import _ "github.com/luxfi/zap/quic"` somewhere in the binary.
Transport Transport
// QUICConfig, if non-nil, is passed to the QUIC transport as a
// *quic.Config (github.com/quic-go/quic-go). Ignored for
// TransportTCP. Typed as any here to avoid pulling quic-go into
// the parent package's import graph.
QUICConfig any
}
NodeConfig configures a ZAP node.
type Object ¶
type Object struct {
// contains filtered or unexported fields
}
Object is a zero-copy view into a ZAP struct.
func (Object) AddressSlice ¶
AddressSlice returns a slice of the address bytes (zero-copy).
func (Object) Bytes ¶
Bytes reads a byte slice at the given field offset (zero-copy).
Wire-format rule: relOffset is an UNSIGNED forward pointer from the field position into the variable-section. Negative bit-patterns (high bit set) flow through uint32→int conversion as large positive values and are rejected by the absPos+length > len(data) bounds check. This closes the memo-pointer-escape malleability surface where a signed cast would let a crafted relOffset alias bytes back inside the fixed section.
func (Object) BytesFixedSlice ¶ added in v0.8.0
BytesFixedSlice returns a zero-copy slice of n inline bytes at fieldOffset within the object's fixed payload. This is the generic- width counterpart of HashSlice (32 bytes) and AddressSlice (20 bytes); it covers any N>0 inline byte-array slot (e.g., LP-201 SessionID [16]byte, LP-208 QuasarWitness [96]byte).
Returns nil if the requested span falls outside the buffer. The returned slice aliases the underlying message data; the caller MUST NOT mutate it.
This is the symmetric reader for ObjectBuilder.SetBytesFixed.
func (Object) List ¶
List reads a list at the given field offset.
Wire-format rule: relOffset is SIGNED (see Object()). RED-HIGH-2: any absOffset < HeaderSize is rejected (lists cannot start inside the wire header). RED-HIGH-1: the length field is bounded by the total message size — an attacker-set length=0xFFFFFFFF would otherwise let downstream `for i := 0; i < l.Len()` loops iterate 4G times even though every per-element accessor would silently return 0.
func (Object) ListStride ¶ added in v0.7.2
ListStride is List() with a caller-supplied per-element stride hint. It applies the tighter clamp `length * minStride <= len(buffer) - absOffset` up front, rejecting attacker-set length=0xFFFFFFFF on multi-byte-stride accessors instead of pushing the bounds check to every per-element accessor.
Use case: an Uint32 list with stride 4, a struct list with stride 96 — pass the stride; the wire layer rejects length values that exceed what the remaining buffer can possibly carry. This is a NEW-V1 follow-up (LP-023 Red round 3) — the bare List() accessor cannot know the stride and uses the permissive `length <= len(data)` baseline.
minStride MUST be the BYTE width of one element (1 for uint8, 4 for uint32, 8 for uint64, SizeTransferableOutput for OutputList, etc.). When minStride <= 0 the call falls back to bare List() semantics.
Wire format is unchanged — same {relOffset, length} pair as List(). The clamp is purely a tightened acceptance test; any List() that would succeed with minStride=0 succeeds with the correct stride too.
func (Object) Message ¶ added in v0.8.0
Message returns the underlying *Message this Object is a view into. Used by zapv2 to alias the message's bytes for direct payload indexing.
func (Object) Object ¶
Object reads a nested object at the given field offset.
Wire-format rule: relOffset is SIGNED. The builder may finalize a nested object BEFORE its parent (in which case the nested payload lives EARLIER in the variable section than the parent's pointer cell, and the relOffset is negative). The bounds check below rejects any absOffset outside the message; for the Bytes-malleability fix see Bytes().
RED-HIGH-2 (LP-023 v3.1 round 2): an attacker can use a backward relOffset to alias the WIRE HEADER (offsets 0..HeaderSize-1). The header carries Magic/Version/Flags/RootOffset/Size — none of which is a legitimate object payload. We reject any absOffset < HeaderSize. The signed-cast still lets honest builders point backward to nested objects they finalized first (which live at offset >= HeaderSize).
func (Object) Offset ¶ added in v0.8.0
Offset returns the object's absolute byte offset within its underlying message. Exposed so external generic wrappers (zapv2) can construct typed sub-views into the same buffer without re-walking the parent pointers.
SAFETY: callers MUST treat the returned offset as opaque — it's load-bearing for unsafe-pointer arithmetic, so any out-of-range usage is the caller's responsibility. The matching message bytes are reachable via Message.Bytes() on this object's message.
type ObjectBuilder ¶
type ObjectBuilder struct {
// contains filtered or unexported fields
}
ObjectBuilder builds a ZAP object (struct).
func (*ObjectBuilder) Finish ¶
func (ob *ObjectBuilder) Finish() int
Finish finalizes the object and returns its offset. Writes deferred text/bytes data after the object's fixed section and patches relative offsets.
func (*ObjectBuilder) FinishAsRoot ¶
func (ob *ObjectBuilder) FinishAsRoot() int
FinishAsRoot finalizes and sets as the message root.
func (*ObjectBuilder) ReserveFixed ¶ added in v0.8.0
func (ob *ObjectBuilder) ReserveFixed(dataSize int)
ReserveFixed extends the builder's write cursor so the object's entire fixed payload of dataSize bytes is materialized (zero-filled up to startPos+dataSize). Use this BEFORE writing any variable- length tail (list elements, deferred bytes) that should live AFTER the fixed section.
Without this call, list elements or deferred-data writes interleave with the unreserved tail of the fixed section, producing incorrect wire bytes when later Set* calls patch fields that overlap with already-written variable data.
Idempotent: a second call with the same dataSize is a no-op. A call with a smaller dataSize is also a no-op (the cursor never moves backwards).
This is the exported counterpart to the internal ensureField helper. It is used by zapv2.WriteList to keep the parent's payload reserved before list elements are appended.
func (*ObjectBuilder) SetAddress ¶
func (ob *ObjectBuilder) SetAddress(fieldOffset int, addr Address)
SetAddress sets an address field.
func (*ObjectBuilder) SetBool ¶
func (ob *ObjectBuilder) SetBool(fieldOffset int, v bool)
SetBool sets a bool field.
func (*ObjectBuilder) SetBytes ¶
func (ob *ObjectBuilder) SetBytes(fieldOffset int, v []byte)
SetBytes sets a bytes field. The data is written after the object's fixed section during Finish().
func (*ObjectBuilder) SetBytesFixed ¶ added in v0.8.0
func (ob *ObjectBuilder) SetBytesFixed(fieldOffset int, v []byte)
SetBytesFixed copies len(v) bytes inline at fieldOffset within the object's fixed payload. This is the generic-width counterpart of SetHash (32 bytes) and SetAddress (20 bytes); it covers any N>0 inline byte-array slot (e.g., LP-201 SessionID [16]byte, LP-208 QuasarWitness [96]byte).
Unlike SetBytes (which writes a variable-length tail pointer {relOffset uint32, length uint32}), SetBytesFixed writes the bytes IN PLACE in the fixed payload. Use this when the schema declares a fixed-width byte field; use SetBytes when the schema declares a variable-length tail.
Panics on len(v) == 0 are avoided: a zero-length argument is a no-op (the slot is left as the zero value).
func (*ObjectBuilder) SetFloat32 ¶
func (ob *ObjectBuilder) SetFloat32(fieldOffset int, v float32)
SetFloat32 sets a float32 field.
func (*ObjectBuilder) SetFloat64 ¶
func (ob *ObjectBuilder) SetFloat64(fieldOffset int, v float64)
SetFloat64 sets a float64 field.
func (*ObjectBuilder) SetHash ¶
func (ob *ObjectBuilder) SetHash(fieldOffset int, h Hash)
SetHash sets a hash field.
func (*ObjectBuilder) SetInt8 ¶
func (ob *ObjectBuilder) SetInt8(fieldOffset int, v int8)
SetInt8 sets an int8 field.
func (*ObjectBuilder) SetInt16 ¶
func (ob *ObjectBuilder) SetInt16(fieldOffset int, v int16)
SetInt16 sets an int16 field.
func (*ObjectBuilder) SetInt32 ¶
func (ob *ObjectBuilder) SetInt32(fieldOffset int, v int32)
SetInt32 sets an int32 field.
func (*ObjectBuilder) SetInt64 ¶
func (ob *ObjectBuilder) SetInt64(fieldOffset int, v int64)
SetInt64 sets an int64 field.
func (*ObjectBuilder) SetList ¶
func (ob *ObjectBuilder) SetList(fieldOffset int, listOffset int, length int)
SetList sets a list field.
func (*ObjectBuilder) SetObject ¶
func (ob *ObjectBuilder) SetObject(fieldOffset int, objOffset int)
SetObject sets a nested object field (by offset).
func (*ObjectBuilder) SetSignature ¶
func (ob *ObjectBuilder) SetSignature(fieldOffset int, sig Signature)
SetSignature sets a signature field.
func (*ObjectBuilder) SetText ¶
func (ob *ObjectBuilder) SetText(fieldOffset int, v string)
SetText sets a text (string) field.
func (*ObjectBuilder) SetUint8 ¶
func (ob *ObjectBuilder) SetUint8(fieldOffset int, v uint8)
SetUint8 sets a uint8 field.
func (*ObjectBuilder) SetUint16 ¶
func (ob *ObjectBuilder) SetUint16(fieldOffset int, v uint16)
SetUint16 sets a uint16 field.
func (*ObjectBuilder) SetUint32 ¶
func (ob *ObjectBuilder) SetUint32(fieldOffset int, v uint32)
SetUint32 sets a uint32 field.
func (*ObjectBuilder) SetUint64 ¶
func (ob *ObjectBuilder) SetUint64(fieldOffset int, v uint64)
SetUint64 sets a uint64 field.
type RegistrationMode ¶ added in v0.6.1
type RegistrationMode int
RegistrationMode selects which signatures VerifyRegistration requires. The chain-state loader decides which to pass based on whether the registry already holds an entry for this VMID.
Passing the wrong mode is a contract bug detectable at the call site: ModeInitial refuses any registration that carries a PrevVMSig; ModeRotation requires both PrevVMSig and PrevVMPubKey. A loader that always calls ModeInitial would let a compromised chain authority overwrite an existing VM key without the old key's consent — which §10.2 forbids.
const ( // ModeInitial: this VMID is being registered for the first time. // PrevVMSig MUST be empty; the chain-authority signature alone // is sufficient. ModeInitial RegistrationMode = iota // ModeRotation: this VMID already exists in the registry; the // caller is replacing its public key. Both the chain-authority // AND the previous VM key must sign the new (VMID, VMPubKey). ModeRotation )
type StaticVMRegistry ¶ added in v0.6.1
type StaticVMRegistry struct {
// contains filtered or unexported fields
}
StaticVMRegistry is an in-memory map implementation of VMRegistry suitable for genesis-loaded registrations. Production deployments will typically back this with an indexed chain-state cache, but the interface stays the same.
func NewStaticVMRegistry ¶ added in v0.6.1
func NewStaticVMRegistry() *StaticVMRegistry
NewStaticVMRegistry returns an empty registry.
func (*StaticVMRegistry) Add ¶ added in v0.6.1
func (r *StaticVMRegistry) Add(reg *VMRegistration)
Add inserts a registration. The caller is responsible for having verified the registration via VerifyRegistration first.
func (*StaticVMRegistry) Lookup ¶ added in v0.6.1
func (r *StaticVMRegistry) Lookup(vmID [32]byte) (*VMRegistration, bool)
Lookup implements VMRegistry.
type StructBuilder ¶
type StructBuilder struct {
// contains filtered or unexported fields
}
StructBuilder helps build struct definitions.
func NewStructBuilder ¶
func NewStructBuilder(name string) *StructBuilder
NewStructBuilder creates a struct builder.
func (*StructBuilder) Address ¶
func (sb *StructBuilder) Address(name string) *StructBuilder
Address adds an address field.
func (*StructBuilder) Bool ¶
func (sb *StructBuilder) Bool(name string) *StructBuilder
Bool adds a bool field.
func (*StructBuilder) Build ¶
func (sb *StructBuilder) Build() *Struct
Build finalizes and returns the struct.
func (*StructBuilder) Bytes ¶
func (sb *StructBuilder) Bytes(name string) *StructBuilder
Bytes adds a bytes field.
func (*StructBuilder) Float64 ¶
func (sb *StructBuilder) Float64(name string) *StructBuilder
Float64 adds a float64 field.
func (*StructBuilder) Hash ¶
func (sb *StructBuilder) Hash(name string) *StructBuilder
Hash adds a hash field.
func (*StructBuilder) Int32 ¶
func (sb *StructBuilder) Int32(name string) *StructBuilder
Int32 adds an int32 field.
func (*StructBuilder) Int64 ¶
func (sb *StructBuilder) Int64(name string) *StructBuilder
Int64 adds an int64 field.
func (*StructBuilder) List ¶
func (sb *StructBuilder) List(name string, elemType Type) *StructBuilder
List adds a list field.
func (*StructBuilder) Signature ¶
func (sb *StructBuilder) Signature(name string) *StructBuilder
Signature adds a signature field.
func (*StructBuilder) Struct ¶
func (sb *StructBuilder) Struct(name string, structName string) *StructBuilder
Struct adds a nested struct field.
func (*StructBuilder) Text ¶
func (sb *StructBuilder) Text(name string) *StructBuilder
Text adds a text field.
func (*StructBuilder) Uint32 ¶
func (sb *StructBuilder) Uint32(name string) *StructBuilder
Uint32 adds a uint32 field.
func (*StructBuilder) Uint64 ¶
func (sb *StructBuilder) Uint64(name string) *StructBuilder
Uint64 adds a uint64 field.
type Transport ¶ added in v0.6.1
type Transport int
Transport selects which network transport a Node uses.
The default zero value, TransportTCP, preserves the historical behavior of NewNode (TCP + optional TLS via NodeConfig.TLS) so every existing caller keeps working untouched.
TransportQUIC selects the QUIC transport defined in the quic subpackage. The quic subpackage must be imported anonymously by the process for TransportQUIC to be available; otherwise NewNode returns ErrTransportUnavailable.
const ( // TransportTCP is ZAP's original transport: framed Cap'n Proto // over TCP, with optional TLS 1.3 supplied via NodeConfig.TLS. // This is the default for backward compatibility. TransportTCP Transport = iota // TransportQUIC selects the QUIC transport from the quic // subpackage. Requires: // // import _ "github.com/luxfi/zap/quic" // // in some package linked into the binary, so the QUIC factory // registers itself at init time. TransportQUIC )
type TransportConn ¶ added in v0.6.1
type TransportConn interface {
// Send writes one ZAP frame to the peer.
Send(frame []byte) error
// Recv blocks until the next ZAP frame arrives, or returns
// io.EOF when the peer cleanly closes.
Recv() ([]byte, error)
// Close performs a graceful close.
Close() error
// RemoteAddr returns the peer's network address (best effort —
// for QUIC this is the address at handshake time, not necessarily
// the latest after migration).
RemoteAddr() string
}
TransportConn is the transport-level abstraction over a single peer connection. It mirrors the existing TCP *Conn semantics (Send/Recv/Close) and is used by the transport-aware Node code path in node.go.
type TransportFactory ¶ added in v0.6.1
type TransportFactory interface {
// Listen starts a listener on the address derived from cfg.
// The listener calls onConn for each accepted, post-handshake
// connection. Listen must not block; it returns the bound
// address (so tests using port 0 can learn the kernel port) and
// a Close func.
Listen(ctx context.Context, cfg NodeConfig, onConn func(peerID string, conn TransportConn)) (string, func() error, error)
// Dial opens a connection to addr. The returned TransportConn
// is symmetric with the conns yielded by Listen.
Dial(ctx context.Context, cfg NodeConfig, addr string) (peerID string, conn TransportConn, err error)
}
TransportFactory is the extension point the quic subpackage uses to register itself with this package at init time, avoiding an import cycle.
A TransportFactory wraps the existing Node so all the higher-level APIs (Handle, Send, Call, Broadcast) work identically regardless of transport. The factory is responsible for:
- Binding a listener on n.port (cfg.Port).
- Yielding accepted connections via the supplied dispatch hook.
- Implementing outbound dial via the supplied dispatch hook.
Today only QUIC uses this hook; TCP is wired directly in node.go because the original Node embeds the TCP listener fields. This is pragmatic — once QUIC ships we can refactor TCP onto the same factory shape without a single user-visible change.
type TransportStream ¶ added in v0.8.0
type TransportStream interface {
WriteFrame(frame []byte) error
ReadFrame() ([]byte, error)
Close() error
}
TransportStream is one per-Call stream. Stream lifecycle:
WriteFrame(req) // single request ReadFrame() // single response Close() // release stream ID back to the QUIC pool
Both directions are length-prefixed ZAP frames identical to the control-stream wire format — so byte-for-byte interop with the TCP transport and with control-stream Call paths is preserved.
type TransportStreamer ¶ added in v0.8.0
type TransportStreamer interface {
// OpenCallStream opens a fresh bidirectional stream for a single
// request/response exchange. The caller writes exactly one frame
// (request) then reads exactly one frame (response) then closes.
OpenCallStream(ctx context.Context) (TransportStream, error)
// AcceptCallStream blocks until the peer opens a per-Call stream.
// Used by the server-side dispatch loop to fan out to handlers
// without blocking on a single serialized read.
AcceptCallStream(ctx context.Context) (TransportStream, error)
}
TransportStreamer is an optional capability extension to TransportConn for transports that natively multiplex independent bidirectional streams (notably QUIC). When a TransportConn also implements TransportStreamer, Node.Call routes each request onto a fresh per-Call stream instead of serializing on the shared control stream — this lets concurrent Calls progress in parallel up to the peer-advertised stream limit (1024 for ZAP's QUIC config).
TCP transport does NOT implement TransportStreamer; Node.Call transparently falls back to control-stream serialization on TCP.
type VMRegistration ¶ added in v0.6.1
type VMRegistration struct {
VMID [32]byte
VMPubKey []byte
AuthoritySig []byte
PrevVMSig []byte
// PrevVMPubKey is the prior VM key for rotation. Verifiers
// supply this out of band (from chain state) when checking
// PrevVMSig. Stored here so a single struct round-trips the
// rotation evidence.
PrevVMPubKey []byte
}
VMRegistration is the §10.2 chain-authority-signed record binding a VM plugin's ML-DSA-65 identity to its VMID.
- VMID : SHA3-256(VMPubKey) — the canonical handle
- VMPubKey : ML-DSA-65 public key bytes (MLDSA65PubLen)
- AuthoritySig : chain-authority ML-DSA-65 signature over (VMID ∥ VMPubKey)
- PrevVMSig : optional — prior VM key's signature over the same payload. Required for rotation; nil/empty for the initial registration.
type VMRegistry ¶ added in v0.6.1
type VMRegistry interface {
Lookup(vmID [32]byte) (*VMRegistration, bool)
}
VMRegistry is loaded from a signed chain config and consulted by the node side of every ZAP-PQ handshake to a VM plugin (§10.2).
A node holds one VMRegistry per chain it serves; the registry is populated from on-chain VMRegistration records each of which is signed by the chain authority.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Real-world benchmark: ZAP vs gRPC/Protobuf
|
Real-world benchmark: ZAP vs gRPC/Protobuf |
|
examples
|
|
|
agents
command
Multi-Agent LLM Consensus via ZAP Protocol
|
Multi-Agent LLM Consensus via ZAP Protocol |
|
mcp-bridge
command
Example: MCP-to-ZAP Bridge with 20 Tool Servers
|
Example: MCP-to-ZAP Bridge with 20 Tool Servers |
|
Package handshake implements SPEC-ZAP-PQ-v1: the native post-quantum handshake and AEAD framing for ZAP.
|
Package handshake implements SPEC-ZAP-PQ-v1: the native post-quantum handshake and AEAD framing for ZAP. |
|
Package mcp provides a bridge between MCP (Model Context Protocol) servers and ZAP for high-performance tool calling.
|
Package mcp provides a bridge between MCP (Model Context Protocol) servers and ZAP for high-performance tool calling. |
|
Package quic implements the QUIC transport for the ZAP messaging substrate.
|
Package quic implements the QUIC transport for the ZAP messaging substrate. |
|
Package transport is the pluggable GPU-aware ZAP transport.
|
Package transport is the pluggable GPU-aware ZAP transport. |
|
Package zapv2 is the elegant, generic, idiomatic Go reference implementation of the ZAP wire format.
|
Package zapv2 is the elegant, generic, idiomatic Go reference implementation of the ZAP wire format. |
|
_compile_fail_test
This file deliberately fails to compile.
|
This file deliberately fails to compile. |
|
codegen
Package codegen emits per-schema ZAP v2 accessors that match v1's hand-rolled inline-everything performance.
|
Package codegen emits per-schema ZAP v2 accessors that match v1's hand-rolled inline-everything performance. |
|
codegen/cmd/zapgen
command
Command zapgen emits per-schema ZAP v2 accessors from a declarative schema description.
|
Command zapgen emits per-schema ZAP v2 accessors from a declarative schema description. |
|
codegen/cmd/zapgen-all
command
Command zapgen-all bulk-emits per-schema ZAP v2 accessor files from a directory of JSON schema declarations.
|
Command zapgen-all bulk-emits per-schema ZAP v2 accessor files from a directory of JSON schema declarations. |
|
examples
Package examples contains worked examples that demonstrate how to express a schema with the v2 generic ZAP API.
|
Package examples contains worked examples that demonstrate how to express a schema with the v2 generic ZAP API. |