Panic-driven denial of service in the AWS SDK for Go v2 EventStream decoder:
eventstream -> v1.7.8, service/s3 -> v1.97.3 (minimum patched versions, so the
change carries the fix and nothing else).
Verified against a measured baseline: go build ./... exit 0, and go test ./...
is 23 ok / 3 fail BOTH with and without this change — identical, so any failures are
pre-existing and none is a regression from this commit.
Only go.mod/go.sum staged; other working-tree changes in this repo are left
untouched.
README.md led with "ThresholdVM is a Go library, not a chain" and LLM.md
instructed agents "do not turn this back into a chain, no new VM ID",
both describing an import graph (chains/mchain, chains/fchain) that does
not exist. While that stood, the VM declared a private thresholdvm vmID
matching nothing else in the stack and the node installed the plugin
under that dead name — M-Chain could not have started.
Rewritten to describe what is actually here: the M-Chain VM, its
canonical vmID and every declaration that must agree with it, the K-vs-t
rule and why the quorum is spelled "3-of-5", the consensus/node-private
state split, what makes a block verifiable, and what is still unproven
(no cross-process DKG yet, no resharing).
Co-authored-by: Hanzo Dev <[email protected]>
Signing used the key's whole participant set, so a 3-of-5 key signed with
five parties. That works, but it never exercises the quorum: a key that
was accidentally degree-3 would sign identically, which is precisely the
failure this VM exists to catch.
RunSign now selects K signers by ranking participants on
H(tag ‖ keyID ‖ digest ‖ party) and taking the first K. The selection is a
pure function of the task, so every node — signer or not — computes the
same quorum and the same ceremony id with no election. Ranking by digest
rather than taking the first K in canonical order spreads signing across
the committee instead of loading the same K parties for every transfer,
and stops an adversary steering which subset signs a message it does not
control. Nodes outside the quorum return ErrNotInQuorum and verify the
result like any other validator.
Verify deliberately does not pin WHICH K-subset signed. The signature
verifies under the group key or it does not, and an adversary who can
produce one already holds K shares, so constraining the subset buys no
security while leaving it open lets availability-aware reselection ship
later without a consensus change.
TestBridgeCustody_ThreeOfFive is the gate: five validators run a real
CGGMP21 DKG over the gossip fabric at degree 2, exactly three sign, two
decline, the signature verifies under the group key, a block carrying it
is verified and accepted by all five — including the two that never
touched the ceremony — and every node ends on the same state root.
Co-authored-by: Hanzo Dev <[email protected]>
M-Chain is the MPC/threshold-custody VM that replaces off-chain signer
clusters for bridge custody. This makes it one.
vmID — the one-way door. factory.go declared a private `thresholdvm`
literal that matched nothing else in the stack, and node/Dockerfile
installed the plugin under that name's CB58 while genesis declares
constants.MPCVMID. The two never met, so M-Chain could not have started.
VMID is now the shared constant, and TestVMID_IsCanonicalAndStable pins
the CB58 (qCURact1n41Fco...) with the list of every declaration that must
move together if it ever changes before launch.
k vs t. types.Ceremony carried Threshold/Total as bare uint16s: Validate
read Threshold as the signer count (enforcing threshold*2 > total) while
the VM read it as the polynomial degree and passed it straight to
cmp.Keygen. The same field meant K in one file and t in another, and the
two differ by one — the same silent off-by-one that produces a 4-of-5
wallet from a config saying 3-of-5. Both now carry a quorum.Policy, and t
is only ever obtained by calling Degree(). The majority floor survives as
types.HasUniqueQuorum, defined once and enforced on every key record.
State. The VM kept its custody keys in a map persisted only on a clean
Shutdown, with the key share excluded from the wire entirely, and reset
lastAccepted to a recomputed genesis block on every Initialize — so a
restart discarded the chain and came back unable to sign. State is now
persisted at the moment each fact becomes true, split into a replicated
c/ keyspace (key registry, ceremony log, blocks, height index, root) and
a node-private n/ keyspace (this validator's shares), and Initialize
resumes the accepted tip.
Blocks verify. A block carries its post-state root and every operation's
artifact. Verify re-checks a signature against the group key already in
the registry — a proposer cannot fabricate one without forging ECDSA — and
recomputes the root, so two validators that would diverge cannot both
accept. Keygen operations carry a proof of possession by the new group key
over its own registration; a participant additionally cross-checks the
declared degree against its own share, so one honest participant rejects a
mis-declared key.
Ceremonies are leaderless. The id is derived from the task
(key, digest, sorted signers), so every validator converges on the same
ceremony with no announce round and no coordinator, and the committee is
the chain's own validator set rather than an operator roster.
Address derivation was SHA-256 where every EVM chain uses Keccak-256, so
the published custody address did not belong to the group key. Funds sent
there would have been unspendable.
Deletes the duplicate signing paths and the in-memory session machinery:
one way to generate a key, one way to sign.
Co-authored-by: Hanzo Dev <[email protected]>
The package exported ExecuteBlockV1, V2, V3, V4 and ExecuteBlock for a single
operation, plus BlockResult and BlockResultV2 for a single result. V2/V3/V4
were pure argument adapters over the same implementation and V1 additionally
downcast the result into a lossy struct, so the versions described how the API
had grown rather than anything a caller needs to choose between.
Now: ExecuteBlock(backend, numThreads, txs, ctx, state) -> *BlockResult.
Callers without a block context or state snapshot pass nil, which is what the
adapters did on their behalf. BlockResult carries the full shape; the lossy
variant is gone. Nothing outside the package used the versioned names.
The duplicate tests went with them — TestExecuteBlockEmpty existed three times
and the backend smoke test twice, one per version, all exercising the same
function. Merged, keeping every distinct assertion.
go vet clean; ./evm/cevm and ./evm/cevm/parallel both pass.
Co-authored-by: Hanzo Dev <[email protected]>
Executes 250 blocks on the Metal backend. Before the luxcpp/cevm fix that
built the device, queue and pipelines once, this exhausted Metal and faulted
inside the driver; the crash needed volume, so no single-execution test caught
it. Runs in about a second.
Co-authored-by: Hanzo Dev <[email protected]>
The Go bindings called gpu_execute_block_v4 / gpu_free_result_v2, which
luxcpp/cevm main does not export — it renamed those to gpu_execute_block /
gpu_free_result. A luxd built CGO_ENABLED=1 against the shipped libs would have
failed to link, not merely mismatched a version. It stayed hidden because
production builds pure-Go and this box had stale branch-built libs installed.
cevm_v4compat.h and cevm_abi_v4compat_cgo.go existed only to bridge that gap —
113 lines whose own comment described the header and library being out of step.
Deleted; cgo already includes the library header directly.
ABIVersion is now taken from that header (C.EVM_GPU_ABI_VERSION) instead of a
hand-maintained Go constant that had drifted to 7 while the header said 6. One
definition, so the two cannot disagree; a runtime panic no longer has to catch
what the compiler can.
go vet clean; ./evm/cevm and ./evm/cevm/parallel both pass.
Co-authored-by: Hanzo Dev <[email protected]>
//go:build grpc importing node/vms/rpcchainvm. Unbuildable (Makefile takes the
cmd/plugin branch), unreferenced, and a second transport where ZAP is the one.
All 11 sibling VMs have cmd/plugin and only cmd/plugin.
Completes the "one and only one way" wire migration: every VM in this repo now
serializes blocks and txs through luxfi/zap struct-is-wire at fixed field
offsets — no encoding/json, no hand-rolled big-endian, no cursor codec.
- aivm: Block + AIVertex + nested CIntent native-encoded. Fixes a latent
data-loss bug — AIVertex had unexported fields, so encoding/json silently
marshalled it as {}; the native encoder round-trips every field.
- keyvm: Transaction (signing preimage = SigningBytes(), excludes Auth/Sig;
full wire = signing || sig object, so the signed bytes stay a genuine prefix
and authenticate() re-derives a byte-identical preimage) + Block.
- quantumvm: Block + BaseTransaction. Block wire is the quantum-signature
preimage (deterministic fixed offsets, ordered tx list).
- dexvm: block envelope (txs were already ZAP). Preserves UnixNano timestamps,
deterministic tx order, and content-addressed carried fills.
Hardening from the adversarial crypto review (must-holds all verified: no
keyvm signature malleability/replay, no aivm CIntent id-forgery/escrow tamper,
no dexvm ordering/fill tamper, all parsers fail closed):
- keyvm (M1): make the wire strictly canonical. zap follows the root offset
and ignores unreferenced padding inside a message's declared size, so a twin
buffer could decode to identical fields with a different hash (id-malleability).
ParseTransaction now binds the id to the re-serialized (canonical) form and
rejects any non-canonical input at the door — exactly one byte-string
authenticates per logical tx. Regression test: the padded twin is rejected.
- quantumvm (M2): derive block + tx ids as sha256(Bytes()), matching the other
three VMs. The prior ids.ToID(parent||height) over 40 bytes (and ids.ToID over
the >=32-byte tx wire) silently yielded ids.Empty for every block/tx,
collapsing the block store and tx pool to a single slot. The block id is no
longer stored in the wire, so the invariant id == sha256(Bytes()) holds
unconditionally. Regression tests: ids are non-Empty and collision-distinct.
Not in scope (tracked separately, fail-closed today): aivm RewardPerOperator is
escrowed but not bound by ComputeIntentID — a pre-existing design gap that needs
a coordinated A-side + C-side intent-id preimage bump before any non-fail-closed
CCommitVerifier ships.
Builds + tests green under the canonical CGO=0 -mod=readonly recipe; go.mod/go.sum
are the go mod tidy canonical set (indirect deps resolved forward, all within
major). Cryptographer verdict: SAFE TO PUBLISH.
Co-authored-by: Hanzo Dev <[email protected]>
dexvm (D-Chain / DEX) is ALREADY 1ms-capable: nanosecond wire timestamps (UnixNano round-trip on
both linear Block and DAG DexVertex paths), Config.BlockInterval default already 1ms ('1ms blocks
for HFT'), deriveBlockHash on UnixNano, no whole-second truncation anywhere. The one artifact was a
dead ChainVM.blockInterval field (100ms, never read repo-wide) that falsely implied a floor — removed
(zero readers → semantically inert). Config.BlockInterval is now the single source of truth. Added two
//go:build redteam tests locking the previously-untested monotonicity clamp at ns resolution across
BOTH proposer entrypoints (BuildVertex + BuildBlock): a 1ms clock advance survives byte-exactly, the
sub-second component is preserved, a backward clock is clamped non-decreasing. Fairness invariants
unchanged (height is the strict total-order sequencer; block time non-decreasing; distinct sub-second
times → distinct block hashes; proposer-carried time, no wall-clock ordering race). Settlement/matching
untouched. Combined with the sub-second proposervm (node v1.36.19 UnixMilli window), a co-located
fiber/InfiniBand DEX runs 1ms blocks natively.
Co-authored-by: Hanzo Dev <[email protected]>
Bumps the plugin subgraph off wiped intermediate tags (was node v1.36.4 → utxo
v0.5.1) onto the current surviving set. dexvm/txs/wire.go: setBase helpers take
zap.ObjectBuilder by value (v1.2.4+ StartObject returns a value type). Full
chains build clean; migrated-VM + mpcvm/bridgevm suites green.
Co-authored-by: Hanzo Dev <[email protected]>
All 5 concrete tx types (ImportTx/ExportTx/RelayOrderTx/PlaceOrderTx/
CancelOrderTx) + nested AtomicInput/AtomicOutput now encode as
[typeByte][ZAP object] at fixed offsets instead of [typeByte][json body].
Repeated fields use the bridgevm blob-list idiom (u32 lens-list + concat blob
via SetList/SetBytes) — chains-module zap v1.2.0 has AddObjectPtr but not the
List.ObjectPtr reader. Deterministic by construction; TxID = Checksum256(wire);
canonical trailing-byte reject. Bench (ImportTx 2in/2out): marshal 18255→912ns
(~20x), parse 14923→498ns (~30x), far fewer allocs. Round-trip + determinism +
signature-through-wire tests green; dexvm redteam/conservation/custody suites
green (parse txs through this codec e2e).
Co-authored-by: Hanzo Dev <[email protected]>
These VMs marshal Config via JSON and wire via hand-rolled/ZAP — nothing reads
the serialize tags (the reflection codec is gone). Pure dead metadata. Full
suites green (byte-identical), confirming no reader existed.
Co-authored-by: Hanzo Dev <[email protected]>
chains pinned github.com/luxfi/[email protected].6 — a tag that no longer exists on the
remote (node's tags were mass-wiped; v1.30.6's lineage is disjoint anyway), which
broke every hermetic build of the VM plugins with 'unknown revision v1.30.6'.
Repin to node v1.36.4 (the current published codec-kill release; all 10 VM
cmd/plugin targets verified compiling against it). No local replace directive.
Realigned genproto so the split googleapis/rpc resolves by longest-prefix (no
monolith ambiguity), matching node's go.mod. tidy + -mod=readonly build clean;
bridgevm/zkvm/mpcvm tests green.
Co-authored-by: Hanzo Dev <[email protected]>
Completes the bridge cross-chain release leg the recon flagged as missing.
- internal/bridgeattest: the ONE canonical definition of the bridge-transfer
attestation seam between B (bridgevm) and M (mpcvm). M threshold-signs a
domain-bound bridge-transfer digest; B and every EVM gateway holding the group
key's address verifies before mint/release. Domain-bound + replay-safe; test
proves a signature over one transfer verifies for THAT transfer and no other.
- evmclient.go: the concrete EVM ChainClient B instantiates per external EVM
(Zoo 200201, Lux testnet C 96368) — chainClients was never populated because
the interface had SendTransaction but no concrete EVM client; B could not
broadcast a release. Now wired via evmByChainID in factory.
- release.go: on a confirmed lock/burn in an accepted block, B builds the
destination release call, obtains + verifies M's threshold attestation, and
broadcasts to the destination gateway. Both directions (Zoo<->Lux).
- attest.go: injects the two runtime deps (Warp->M, KMS relayer key) as clean
interface boundaries rather than hard-wiring transports.
Co-authored-by: Hanzo Dev <[email protected]>
Kills node/vms/pcodecs from the M-Chain (MPC custody VM) — the LAST of the three
app-chain VMs blocking pcodecs deletion. bridgevm + zkvm already native.
Codec kill (wire.go): the legacy pcodecs LinearCodec keyed on serialize:"true"
tags; every mpcvm type carries only json: tags, so the old codec marshaled ZERO
fields — a Block round-tripped to all-zero and every block ID collided at
sha256(0x0000). This is a latent corruption bug the native wire FIXES. wire.go
owns Marshal/parse for Block (nested []*Operation as u32-length-list + concat
sub-object blob), ManagedKey (PartyIDs []party.ID as packed string list;
CreatedAt/LastUsedAt as UnixNano; Config/CMPConfig json:"-" excluded),
CrossChainMPCRequest, Operation. Block.ID_ is derived (computeID=sha256(Marshal))
so it is excluded from wire; recomputed on ParseBlock, set from DB key on getBlock.
ThresholdConfig (init.Config) + Genesis stay JSON at the boundary. Deleted
mpcvm/codec.go; swapped 9 call sites. Parse rejects trailing bytes. Round-trips
green (Operation/Block/ManagedKey/CrossChainMPCRequest incl empty-ops nil-slice).
Threshold signing (sign_distributed.go + transport/attest_bridge): CGGMP21/CMP
threshold-ECDSA ceremony across the M committee over native app-gossip; each
validator runs its own executor + per-session gossipRouter; honest signers derive
the SAME standard secp256k1 signature. B→M attestation seam is domain-bound and
replay-safe. Tests: transport-free seam (bridge_seam_test) + full 3-validator
in-process fabric driving the real Broadcast→Gossip→router.Deliver path
(bridge_transport_test).
3 of 3 chains VMs native. chains repo now has ZERO pcodecs imports.
Co-authored-by: Hanzo Dev <[email protected]>
Block + BridgeRequest get native zap Marshal/Parse (offset objects; requests
as packed length-prefixed wire objects; MPC sig map as sorted nodeID blob +
length-prefixed sig blob). Genesis stays JSON at the config boundary (vm.go).
Deleted bridgevm/codec.go (linearcodec via node/vms/pcodecs). Parse rejects
trailing bytes (canonical). No pcodecs, no reflection, no codec registry.
Build + tests green. 1 of 3 chains VMs (zkvm/mpcvm remain) that block deleting
the node's pcodecs package.
Co-authored-by: Hanzo Dev <[email protected]>
Kills the stale evm v1.99.51 / precompile v0.16.0 (matcher-bearing) pins. chains
now transitively carries precompile v0.19.1 (settle-only 0x9999, no embedded
matcher). go.sum regenerated (rm + go mod tidy -e; the -e tolerates a pre-existing
genproto/googleapis/rpc/status TEST-import ambiguity in aivm/cmd/plugin, unrelated
to this bump — chains builds clean). luxfi re-tag checksums (zap/age) refreshed.
Co-authored-by: Hanzo Dev <[email protected]>
chains pinned consensus v1.25.35, whose protocol/quasar/polaris.go imports
the OLD github.com/luxfi/pulsar/ref/go/pkg/pulsar path that pulsar v1.9.0
removed (it moved to pulsar/pkg/pulsar). The quantumvm + mpcvm plugins —
which transitively pull that consensus — failed to build in the node
Docker image ("module luxfi/pulsar found, but does not contain package
.../ref/go/pkg/pulsar"), breaking the node:v1.34.10/11 image builds.
Bump to consensus v1.35.22 (current path + the ProposalKey liveness fix).
go mod tidy resolves a consistent graph; all 10 chain VM plugins
(aivm/bridgevm/graphvm/identityvm/keyvm/oraclevm/quantumvm/relayvm/mpcvm/
zkvm) build clean standalone (GOWORK=off, -mod=mod, verified).
Co-authored-by: Hanzo Dev <[email protected]>
B-Chain does NOT do threshold custody keygen/signing. Custody is
M-Chain's job: dealerless CGGMP21/FROST distributed keygen via
luxfi/threshold (cmp.Keygen/frost.Keygen — multi-round protocols over a
message router; no trusted dealer, the group secret is never assembled
by any validator). B-Chain requests keygen/reshare/sign from M-Chain
over Warp (CrossChainMPCRequest) and only VERIFIES the resulting CMP
threshold signatures (block.Verify → mpcConfig, accel_verify.go).
bridgevm carried a SECOND, parallel MPC subsystem — a BLS MPCKeyManager
that did its own in-VM keygen (until this session, via a trusted dealer)
feeding BridgeSigner/DeliveryConfirmationSigner/BridgeMessageValidator.
It duplicated custody, used the wrong scheme (BLS vs the live CMP path),
and was never in block.Verify. Deleted entirely — one and one way.
Removed:
- mpc.go (MPCKeyManager, SigningSession)
- bridge_signing.go (BridgeSigner, DeliveryConfirmationSigner,
BridgeMessageValidator, MPCCoordinator, BridgeMessage/DeliveryConfirmation)
- vm_mpc_integration.go (InitializeMPCKeys, TriggerKeygen, ProcessBridgeMessage)
- vm.go: the 5 BLS fields + their init block (no key material is
generated on B-Chain now)
- feegate.go: gateUserBridgeMessage (BLS-message fee gate) → replaced by
gateUserBridgeFee(uint64) on the live path (LP-0130 §8)
Relocated / repointed:
- ErrInvalidBridgeSignature → accel_verify.go (the live CMP verify path)
- rpc.go MPCReady/MPCPublicKey → vm.mpcGroupPublicKey() (reads the CMP
custody group point from mpcConfig, i.e. M-Chain's key), never a
B-Chain-held secret
Build + vet + full bridgevm tests green.
Co-authored-by: Hanzo Dev <[email protected]>
B-Chain is a public permissionless chain; custody keygen must never
run through a trusted dealer. GenerateKeys now runs a dealerless
Pedersen-VSS DKG (no party holds the group secret) instead of
scheme.NewTrustedDealer. Build + bridgevm MPC tests pass.
This is the one production chains/node path that USED a trusted dealer
(the dealer remains available as a library option in crypto/threshold
per CTO direction — it is simply never used by chains/node).
Follow-up (threshold-architecture consolidation): route this keygen
through luxfi/dkg, the canonical dealerless DKG engine, once B-Chain
custody's signature scheme (classical BLS vs PQ corona/pulsar) is
fixed.
Co-authored-by: Hanzo Dev <[email protected]>
Flip the Q-Chain fee gate from FlatPolicy (user-fee metering) to the
committee-only fee.NoUserTxPolicy sentinel. Finality-cert inclusion is
a validator obligation paid via P-Chain reward distribution; a fee
market on Q would make finality hostage to blockspace pricing — the
exact failure mode LP-0130 §6 eliminates. Consensus-internal cert
aggregation reaches txPool.AddTransaction directly and is unaffected.
Tests updated: policy is the NoUserTxPolicy sentinel and IssueTx
refuses every user tx (zero-fee, floor-fee, and 1000x floor) with
ErrChainAcceptsNoUserTxs. go test ./quantumvm/ passes; build + vet
clean.
Closes the top divergence from the 2026-07-03 LP-vs-code audit.
Co-authored-by: Hanzo Dev <[email protected]>
Under LP-0130's canonical roster, chains/thresholdvm/ hosts the M-Chain
VM (MPC threshold signing). The package name "thresholdvm" was the last
piece of T-Chain nomenclature still shipping in code — legacy from the
pre-LP-134 shared-substrate design. Renamed to mpcvm/ so the package
name matches the M-Chain letter and LP-7100 spec.
- chains/thresholdvm/ → chains/mpcvm/ (dir + inner files + package decl)
- Sed-replace thresholdvm → mpcvm across .go / .md / .yml / .json / .sh
- Fee/settlement primitive references now say mpcvm
- LLM.md canonical chain roster (LP-0130) already points at mpcvm/
F-Chain runtime pieces (fhe/, protocol/tfhe_keygen/,
runtime/f_chain_adapter.go) stay under mpcvm/ for now — they consume
the same shared threshold primitives. A follow-up extract to
chains/fhevm/ can happen once F-Chain runtime graduates.
Co-authored-by: Hanzo Dev <[email protected]>
The decomplection proper: separate what the threshold primitive IS from where
it is APPLIED (LP-134 / LP-7050). service.go carves three orthogonal service
interfaces directly from the methods the single overloaded *VM already
implements:
- ThresholdService — pure primitives (DKG/keygen/refresh/reshare, key lookup).
The substrate M and F both consume.
- MPCService (embeds ThresholdService) — threshold signing + bridge-custody
attestation. M-Chain's surface (LP-7100).
- FHEService (embeds ThresholdService) — confidential compute over encrypted
state. F-Chain's surface (LP-8200); FHE-execution methods are added as the
physical fhevm package is carved out.
*VM is asserted to satisfy all three at compile time (the compatibility
bridge). This changes NO behavior and NO genesis — the full test suite stays
green — but it establishes the exact interface boundary the physical VM split
(github.com/luxfi/chains/{mpcvm,fhevm} each backed only by the substrate it
needs) will implement, so the split cannot silently reach across surfaces.
service_test.go locks the embedding relationship (MPC/FHE ⊃ Threshold) so a
future split can't drop the substrate dependency.
Co-authored-by: Hanzo Dev <[email protected]>
thresholdvm is a shared LIBRARY substrate, not a chain. Fix the stale/false
naming across the package to the canonical model:
- factory.go: the VMID comment claimed "T-Chain is reserved for TeleportVM,
not yet built" — FALSE per LP-7050. There is no teleportvm and no T-Chain;
teleport IS bridgevm (B-Chain, LP-6000). Rewrote to describe the value as
the shared substrate VM consumed by M-Chain (mpcvm) + F-Chain (fhevm).
- vm.go: package doc dropped the "mode"/"T-Chain label" framing for the
library-substrate description; both M and F consume it, neither is T.
- README.md / DESIGN.md / docs/M_CHAIN_INTEGRATION.md: removed the repeated
false claim that the "T-Chain" name is retained for a standalone
teleportvm (LP-6332). LP-6332 is superseded by LP-6000 (bridgevm) per
LP-7050 §3. Reframed the M/F split as two chains consuming this library
rather than one VM switched by mode.
- LLM.md: corrected the "deprecated T-Chain shim / one-epoch grace-window
migration" prose — T-Chain was never launched with live state (clean
forward split, no migration), and vm.go/factory.go/fhe/ are the shared
library substrate M/F consume, not a T-Chain shim.
No code behavior changes; comments/docs only.
Co-authored-by: Hanzo Dev <[email protected]>
The dex venue (v1.14.0) froze its ZAP wire to EXACT INTEGERS: order/fill
price is uint64 fixed-point ×1e8 (the PriceInt grid), size is uint64 atomic
base units — no IEEE-754 float64. This eliminated every value-bearing
float→int conversion from the settlement path, whose out-of-range result is
implementation-defined per the Go spec and differs by CPU arch (arm64
saturates, amd64 wraps) — the cross-arch consensus fork (F1-F5).
The stateless atomic ZAP proxy still encoded/decoded the OLD float wire, so
it could no longer interoperate with the new venue. This propagates the
frozen frame byte-identically:
- relay.go: Fill{Price,Size} float64 -> uint64; DecodeFills reads big-endian
uint64s and rejects zero price/size. New PriceScale const == zapwire.PriceScale.
- carried_fills.go: block-carried fills encode/decode as exact uint64s.
- atomic.go: drop the float→int rounding machinery (quantToCredit/quantToCharge/
nearestIntWithin/settlementRoundEpsilon/maxSettlementUnit/float64FromBits/
isFinitePositive). Add quoteUnits (floor(size×price/PriceScale) in big.Int,
mirroring dex v4BalanceDelta / the matcher's quoteUnitsFor) and settleUint64
(refuses an aggregate that exceeds uint64 rather than wrapping).
- vm.go settleFromFills: aggregate base=Σsize, quote=Σ per-fill floor as exact
integers; slippage limit compared as fixed-point uint64; no float. Refund/
proceeds are the venue's own integers — escrow-truncation mint is structurally
impossible; spent>locked still refused.
- vm.go encodeCLOBPlace: price = tx.Price×PriceScale, size passthrough (no float).
- feegate.go: swapNativeFeeToLUX takes a fixed-point Fill.Price and floors via
quoteUnits.
- txs/tx.go: RelayOrderTx.PriceLimit is fixed-point ×PriceScale (was float64-bits).
Dep: github.com/luxfi/dex v1.5.20 (indirect) -> v1.14.0 (direct, test imports
the pure-Go zapwire leaf as the parity oracle).
Tests: all conservation/atomicity/redteam tests translated to the integer
semantics WITHOUT weakening any invariant (the no-mint / overflow-refusal /
mixed-side / slippage / determinism proofs are preserved, re-anchored to the
venue's exact per-fill floor). New frame_parity_test.go asserts the proxy's
Place/Submit/Fill frames are BYTE-IDENTICAL to dex v1.14.0 zapwire.
Co-authored-by: Hanzo Dev <[email protected]>
Add redteam-tagged Byzantine settlement tests proving the chains/dexvm
conservation bound holds even against a genuinely-signing byzantine venue
(co-located with the proposer, so every fabricated fill carries a VALID
Ed25519 attestation). The attestation gate proves a fill came from the
venue key; these prove that even a validly-signed fill cannot mint.
dexvm/byzantine_settlement_test.go (//go:build redteam):
- OverNotionalAttestedFillCannotMint: a validly-attested BUY spending
1200 quote against 1000 locked is refused by settleFromFills
("spent > locked"); 0 minted, escrow intact (keystone)
- WithinCollateralAttestedFillSettles: positive control — same venue,
a within-collateral fill settles (400 base + 200 refund). The
discriminator is spent<=locked, not the attestation
- MixedSideAttestedFillRefused: a validly-attested [BUY,SELL] stream is
refused by the single-side guard; no over-credit
- DeterministicRegardlessOfFillOrder: settle is a pure function of the
fill SET — two orderings settle to byte-identical export legs (RED #9)
Co-authored-by: Hanzo Dev <[email protected]>
Picks up the WaitForEvent builder-ready race fix + target-rate build pacing
that un-freezes the C-Chain (it stalled at the imported frontier: WaitForEvent
blocked forever on a nil builder, and too-early high-fee blocks rejected
small-tip txs). Realigned go.sum for the upstream luxfi/age v1.5.0 tag-move
(zip content hash G69H... -> zC/Fw...; matches evm v1.99.51 + the live module
source). C-Chain VM plugin builds clean.
Standardize all of chains on github.com/luxfi/warp v1.23.0 (the standalone
ZAP-native warp that precompile/evm/bridgevm already use). zkvm/fhe and
thresholdvm/fhe are migrated OFF github.com/luxfi/node/vms/platformvm/warp;
there are now ZERO platformvm/warp imports in chains.
The build->sign->wrap sequence (previously copy-pasted in three places and
split across two warp libraries) now lives in exactly one place:
internal/warpmsg.BuildSigned(signer, networkID, sourceChainID, payload)
-> (*warp.Envelope, error). It builds a content Message (NewMessage), signs
it over the Beam domain with the node BLS key, and wraps the signature at
validator bit index 0 into a single-signer Envelope. All three send sites
call it:
- bridgevm/vm.go reshare-request gossip
- zkvm/fhe/coprocessor FHE task-result callback
- thresholdvm/fhe/relayer decryption fulfillment
v1.23.0 split the signature from the Message: the signed/transmittable unit
is now *warp.Envelope (Message + Beam BitSetSignature + PQ lanes), not a
combined Message. The receive path moves with it: onMessage callbacks and
WarpHandler.HandleMessage now carry *warp.Envelope and read authenticated
content from env.Message. MPC/BLS single-signer semantics are preserved
exactly (bit-0 signer, Beam-domain BLS, receiver aggregates/verifies the
BitSetSignature against the canonical validator set via warp.VerifyEnvelope).
CGO_ENABLED=0 build/vet clean; bridgevm/zkvm/thresholdvm test suites green.
Corona is the Ringtail/Raccoon Module-LWE threshold scheme
(q=0x1000000004A01, module dims M=8,N=7 over Z_q[X]/(X^256+1)) — a
relic "Ring-LWE" label in the M-Chain general-purpose lane package doc.
Comment-only; interface-only file, no behavior change.
InitializeDualThreshold called consensus quasar.GenerateDualKeys, a
trusted-dealer keygen (one process mints+holds every validator's BLS+Corona
share, defeating threshold security). Consensus relocated that helper to
test-only (keygen_testsupport_test.go) as corona-genesis hardening, so
production chains no longer compiles against it.
The method was dead: zero callers anywhere, and it already discarded the
generated signer (_ = signer, used only for two log lines) and inited fake
v0/v1/v2 validator IDs. The live Quasar bridge signs through the consensus
core with per-validator keys (ML-DSA fallback on the VM paths); real
validators join via AddValidator. Removing it is a no-op at runtime and
restores the build against consensus v1.25.35. Any future per-epoch group
key must come from a dealerless DKG, never a trusted dealer.
The G-Chain VM declared chain.ChainVM but stubbed the block methods:
GetBlock/ParseBlock returned errNotImplemented, LastAccepted returned the
unset preferredID (ids.Empty), and Block.Status() returned choices.Status
(not the uint8 the block.Block interface requires, so *Block never satisfied
the interface).
At boot the ZAP VM server (vm/rpc/vm_server_zap.go handleInitialize) calls
LastAccepted() then GetBlock(lastAccepted); the GetBlock stub produced
'get last accepted block: not implemented', failing G-Chain initialization
and the lux-mainnet node health check.
The G-Chain is a read-only query/index VM that never advances past genesis,
but consensus still requires a resolvable last-accepted block. Build a
deterministic genesis block (height 0, content-addressed ID = SHA-256 of its
canonical encoding) in Initialize, pin it as lastAccepted, and serve it from
GetBlock/ParseBlock/GetBlockIDAtHeight. Fix Status() to return uint8 and add
the var _ block.Block compile assertion so this can't regress.
Tests reproduce the exact handleInitialize sequence plus byte/ID round-trip
and cross-validator determinism.
D-Chain fees denominate in the ONE canonical L0 asset constants.UTXO_ASSET_ID (the
brand-neutral LUX UTXO literal), not the local network's UTXO asset. So any L2/L3
building on LUX with its own native currency still settles DEX fees to LUX. The fee
policy is network-independent (newFeePolicy() takes no networkID); the MinTxFeeFloor
gate is enforced in LUX on every chain. UTXO_ASSET_ID is the correct reference (D-Chain
is UTXO; an EVM-context DEX would use the EVM-native LUX). networkID retained as chain
identity only. Tests: fee asset is canonical LUX not local native; floor holds on an L2;
native->LUX deterministic; zero-fee rejected; min-fee accepted. dexvm green.
The dexcore->dex refactor gutted the chain-label scan in AssertNoForbiddenAssetRefs
(it discarded chainLabel via '_ = chainLabel'), so the DEX startup gate no longer
refused a source chain labeled a 'Liquidity (white-label) universe' even though the
token was real on-chain — a regression of the never-mix-Liquidity boundary.
Restore the negative gate: IsForbiddenUniverseLabel scans the chain's human label for
forbidden off-network brands (liquid -> catches Liquid EVM/DEX/Liquidity; partner),
case-insensitive, and AssertNoForbiddenAssetRefs refuses by name. Positive ChainVerifier
proves existence; this polices brand. Closes the 3 regressed gate tests.