Files
zeekay 45df2649d9 feat: ZAP-native wire schemas — activation 2025-12-25T16:20:00-08:00
Adds github.com/luxfi/utxo/wire package + per-fx wire.go adapters so
that luxfi/node's vms/{platformvm,xvm}/txs/codec.go can be deleted.

wire/ package mirrors luxfi/node/vms/wire — every fxs primitive carries
a (TypeKind, ShapeKind) discriminator pair on its wire envelope, then
a ZAP message describing its fields. Decomposes the legacy
codec.Manager slot map (which braided "fx family" and "primitive shape"
into a single dense uint32 slot id) into two orthogonal 1-byte tags.

PQ-specific schemas (pq_output_owners, pq_transfer_output,
pq_mint_output) handle the mldsafx/slhdsafx case where addresses are
full PQ pubkeys (1312-2592 bytes) rather than 20-byte hashed ShortIDs.
The wire layer is stride-agnostic — the per-fx wire.go resolves
SecurityLevel → pubkey stride before parsing.

Per-fx wire.go adapters provide (value T).Bytes() []byte (Go type →
wire envelope) and Wrap*(b []byte) (*T, error) (wire envelope → Go
type). The Initialize(vm)/codec.Manager.RegisterType chain stays in
place pending downstream coordination; this commit gets the wire/
package + adapters in so consumer agents (platformvm/warp,
platformvm/state, xvm) can pin a SHA and start their migrations.

UTXO.WireBytes() builds the outer envelope by calling the polymorphic
Out's Bytes() (any fxs primitive implementing wireSerializable).
WrapUTXOBytes(b) parses the outer envelope; the inner output is
dispatched by the caller via OutputDiscriminator() → fx-package
WrapTransferOutput / WrapMintOutput / WrapAttestationOutput.

Tests: 35 round-trip tests across 9 packages. go vet clean. go test
-race clean. gofmt clean.

Activation: 2025-12-25T16:20:00-08:00 (unix 1766708400). No
backwards compat — pre-activation legacy codec blobs are a
protocol-violation reject after activation.
2026-06-03 09:34:23 -07:00

202 lines
5.5 KiB
Go

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ed25519fx
import (
"github.com/luxfi/utxo/wire"
)
// TypeKind is the wire-level discriminator for every ed25519fx
// primitive's wire envelope.
const TypeKind = wire.TypeKindEd25519
// Bytes returns the ZAP-native wire envelope for this OutputOwners.
// Envelope = (TypeKindReserved, ShapeKindOutputOwners, ZAP message) —
// owners are not fx-owned (same wire payload across every fx with a
// multi-ShortID ownership group).
func (out *OutputOwners) Bytes() []byte {
return wire.NewOutputOwners(wire.OutputOwnersInput{
Locktime: out.Locktime,
Threshold: out.Threshold,
Addresses: out.Addrs,
})
}
// WrapOutputOwners parses a wire envelope into a fresh OutputOwners.
func WrapOutputOwners(b []byte) (*OutputOwners, error) {
v, err := wire.WrapOutputOwners(b)
if err != nil {
return nil, err
}
return &OutputOwners{
Locktime: v.Locktime(),
Threshold: v.Threshold(),
Addrs: v.AddressList().All(),
}, nil
}
// Bytes returns the ZAP-native wire envelope for this TransferOutput.
func (out *TransferOutput) Bytes() []byte {
return wire.NewTransferOutput(wire.TransferOutputInput{
TypeKind: TypeKind,
Amount: out.Amt,
Locktime: out.Locktime,
Threshold: out.Threshold,
Addresses: out.Addrs,
})
}
// WrapTransferOutput parses a wire envelope into a fresh TransferOutput.
func WrapTransferOutput(b []byte) (*TransferOutput, error) {
v, err := wire.WrapTransferOutput(b)
if err != nil {
return nil, err
}
if v.TypeKind() != TypeKind {
return nil, wire.ErrWrongTypeKind
}
return &TransferOutput{
Amt: v.Amount(),
OutputOwners: OutputOwners{
Locktime: v.Locktime(),
Threshold: v.Threshold(),
Addrs: v.AddressList().All(),
},
}, nil
}
// Bytes returns the ZAP-native wire envelope for this TransferInput.
func (in *TransferInput) Bytes() []byte {
return wire.NewTransferInput(wire.TransferInputInput{
TypeKind: TypeKind,
Amount: in.Amt,
SigIndices: in.SigIndices,
})
}
// WrapTransferInput parses a wire envelope into a fresh TransferInput.
func WrapTransferInput(b []byte) (*TransferInput, error) {
v, err := wire.WrapTransferInput(b)
if err != nil {
return nil, err
}
if v.TypeKind() != TypeKind {
return nil, wire.ErrWrongTypeKind
}
return &TransferInput{
Amt: v.Amount(),
Input: Input{SigIndices: v.SigIndices()},
}, nil
}
// Bytes returns the ZAP-native wire envelope for this MintOutput.
func (out *MintOutput) Bytes() []byte {
return wire.NewMintOutput(wire.MintOutputInput{
TypeKind: TypeKind,
Locktime: out.Locktime,
Threshold: out.Threshold,
Addresses: out.Addrs,
})
}
// WrapMintOutput parses a wire envelope into a fresh MintOutput.
func WrapMintOutput(b []byte) (*MintOutput, error) {
v, err := wire.WrapMintOutput(b)
if err != nil {
return nil, err
}
if v.TypeKind() != TypeKind {
return nil, wire.ErrWrongTypeKind
}
return &MintOutput{
OutputOwners: OutputOwners{
Locktime: v.Locktime(),
Threshold: v.Threshold(),
Addrs: v.AddressList().All(),
},
}, nil
}
// Bytes returns the ZAP-native wire envelope for this MintOperation.
func (op *MintOperation) Bytes() []byte {
return wire.NewMintOperation(wire.MintOperationInput{
TypeKind: TypeKind,
SigIndices: op.MintInput.SigIndices,
MintOutput: op.MintOutput.Bytes(),
TransferOutput: op.TransferOutput.Bytes(),
})
}
// WrapMintOperation parses a wire envelope into a fresh MintOperation.
func WrapMintOperation(b []byte) (*MintOperation, error) {
v, err := wire.WrapMintOperation(b)
if err != nil {
return nil, err
}
if v.TypeKind() != TypeKind {
return nil, wire.ErrWrongTypeKind
}
mintOutput, err := WrapMintOutput(v.MintOutputBytes())
if err != nil {
return nil, err
}
transferOutput, err := WrapTransferOutput(v.TransferOutputBytes())
if err != nil {
return nil, err
}
return &MintOperation{
MintInput: Input{SigIndices: v.SigIndices()},
MintOutput: *mintOutput,
TransferOutput: *transferOutput,
}, nil
}
// Bytes returns the ZAP-native wire envelope for this Credential. Ed25519
// is not pubkey-recoverable from a signature, so the credential carries
// pubkeys alongside signatures. Both are concatenated into a single byte
// run with fixed per-element strides (SigLen / PubKeyLen).
func (cr *Credential) Bytes() []byte {
sigsConcat := make([]byte, 0, len(cr.Sigs)*SigLen)
for _, sig := range cr.Sigs {
sigsConcat = append(sigsConcat, sig[:]...)
}
pksConcat := make([]byte, 0, len(cr.PubKeys)*PubKeyLen)
for _, pk := range cr.PubKeys {
pksConcat = append(pksConcat, pk...)
}
return wire.NewCredential(wire.CredentialInput{
TypeKind: TypeKind,
SecurityLevel: 0,
Signatures: sigsConcat,
PubKeys: pksConcat,
})
}
// WrapCredential parses a wire envelope into a fresh Credential. Each
// signature is exactly SigLen bytes; each pubkey is exactly PubKeyLen
// bytes.
func WrapCredential(b []byte) (*Credential, error) {
v, err := wire.WrapCredential(b)
if err != nil {
return nil, err
}
if v.TypeKind() != TypeKind {
return nil, wire.ErrWrongTypeKind
}
n := v.SignatureCount(SigLen)
sigs := make([][SigLen]byte, n)
for i := 0; i < n; i++ {
raw := v.SignatureAt(i, SigLen)
copy(sigs[i][:], raw)
}
pkBytes := v.PubKeyBytes()
pks := make([][]byte, 0, len(pkBytes)/PubKeyLen)
for i := 0; i+PubKeyLen <= len(pkBytes); i += PubKeyLen {
entry := make([]byte, PubKeyLen)
copy(entry, pkBytes[i:i+PubKeyLen])
pks = append(pks, entry)
}
return &Credential{Sigs: sigs, PubKeys: pks}, nil
}