369 Commits
Author SHA1 Message Date
zeekay 4d3e4c6cc1 fix(deps): patch the AWS SDK EventStream DoS
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.
2026-07-26 07:57:03 -07:00
zeekayandHanzo Dev cd4972ec28 mpcvm: docs said this was not a chain; it is M-Chain
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]>
v1.7.11
2026-07-25 15:09:22 -07:00
zeekayandHanzo Dev f01758f3a8 mpcvm: sign with the policy's quorum, and prove 3-of-5 end to end
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]>
v1.7.10
2026-07-25 14:45:13 -07:00
zeekayandHanzo Dev 1c7f329fbd mpcvm: make M-Chain a real custody chain
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]>
2026-07-25 14:36:22 -07:00
zeekayandHanzo Dev a8980c537b cevm: one entry point, one result type
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]>
2026-07-25 13:34:03 -07:00
zeekayandHanzo Dev 5cc29b3da0 cevm: regression test for Metal resource exhaustion
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]>
2026-07-25 13:20:09 -07:00
zeekayandHanzo Dev cb072af0c3 cevm: bind to the clean unversioned C ABI, drop the compat shim
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]>
2026-07-25 13:19:09 -07:00
zeekay 8d0335a46b chore(chains): remove dexvm/plugin — gRPC transport, ZAP-native only
//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.
2026-07-25 12:26:39 -07:00
zeekayandHanzo Dev 9ec41cf150 Native-ZAP struct-is-wire for the four straggler VMs (aivm/keyvm/quantumvm/dexvm)
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]>
v1.7.9
2026-07-16 13:44:24 -07:00
zeekayandHanzo Dev 7786bf1a56 dexvm: remove dead blockInterval field; lock ns-resolution sub-second cadence + monotonic clamp
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]>
v1.7.8
2026-07-16 11:01:36 -07:00
zeekay 5f992370d3 chore(deps): bump geth v1.20.1 + luxfi deps — stack unification v1.7.7 2026-07-15 11:17:08 -07:00
zeekayandHanzo Dev 17af562c54 deps: realign to node v1.36.9 / utxo v0.5.7 / zap v1.2.5 (surviving tags)
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]>
v1.7.6
2026-07-14 15:55:09 -07:00
zeekayandHanzo Dev 2c8810fde4 graphvm: migrate block wire json→native ZAP (G-Chain codec-free)
The G-Chain block Bytes()/ID path used json.Marshal(blockWire{...}); now a
fixed-offset ZAP object (ParentID 32B, Height u64, Timestamp i64, Payload bytes).
blockID = hash(canonical ZAP wire), deterministic across nodes/restarts, trailing
bytes rejected. Dropped encoding/json from block.go. Round-trip + determinism +
trailing-byte tests green.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-14 15:39:27 -07:00
zeekayandHanzo Dev c7e0109555 identityvm: migrate block/credential wire json→native ZAP (I-Chain codec-free)
Block, Credential, CredentialProof, Identity, ServiceEndpoint, RevocationEntry
all encode as fixed-offset ZAP objects (nested lists via u32-lens + concat blob,
mpcvm/bridgevm idiom). blockID = sha256(canonical ZAP wire). block.Bytes(),
computeID, and the 3 vm.go block-parse sites + identity/revocation persistence
all native ZAP; dropped encoding/json + encoding/binary from block.go.
HOLDOUT (correct): Credential.Claims is a schemaless map[string]interface{} VC
document — carried as a canonical key-sorted JSON blob inside a ZAP bytes slot
(encoding arbitrary dynamic JSON 'natively' = re-introducing a reflection codec).
Structural wire is 100% ZAP. 34/34 tests green (round-trip all fields,
determinism incl multi-key claims canonicalization, VM parse-block path).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-14 15:35:52 -07:00
zeekayandHanzo Dev 3769a8ab27 dexvm: migrate tx wire json→native ZAP (~20-30x faster, D-Chain codec-free)
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]>
2026-07-14 15:35:09 -07:00
zeekayandHanzo Dev cf6e2972c0 schain: migrate tx wire json→native ZAP (last codec on S-Chain)
PutManifestTx + AllocateTx now encode as [typeByte][ZAP object] instead of
[typeByte][json body]. Fixed-offset ZAP objects (FileIDs/pubkey/sig as
variable tails, NodeID/Fingerprint as fixed bytes) — deterministic by
construction, TxID = Checksum256(wire). Parser dispatches on data[0], rejects
trailing bytes (canonical). Removed the unused generic json Marshal[T].
11 txs tests green (round-trip all fields incl ML-DSA pubkey/sig, determinism,
trailing-byte + bad-type rejection); schain/pinning/state suites green.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-14 15:32:59 -07:00
zeekayandHanzo Dev ce3cd0b35b strip 40 dead serialize:"true" tags (aivm/zkvm/graphvm/mpcvm)
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]>
2026-07-14 15:14:30 -07:00
zeekayandHanzo Dev 42fae205b8 deps: pin node v1.36.4 (real tag, was wiped v1.30.6) + realign genproto
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]>
v1.7.5
2026-07-12 21:36:03 -07:00
zeekayandHanzo Dev 4ba9544b51 bridgevm: B<->M release path + canonical attestation seam
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]>
v1.7.4
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev d682216d8c mpcvm: native ZAP struct-is-wire + cross-validator threshold signing
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]>
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev fc6083cbae zkvm: native ZAP struct-is-wire — kill node/vms/pcodecs dependency
Z-Chain fully native: wire.go with Marshal/parse for Transaction (nested
TransparentInput/Output, ShieldedOutput, ZKProof, FHEData; Nullifiers [][]byte;
optional-pointer proofs as presence-by-length), Block, UTXO, PrivateAddress.
Nested slices packed as u32-length-list + concat sub-object blob; generic
packObjs/unpackObjs + packBytesList helpers. ZConfig + Genesis are JSON at the
init.Config/init.Genesis boundary (dropped codec fallback). Deleted zkvm/codec.go;
swapped 10 call sites. Parse rejects trailing bytes. Round-trip tests (incl deep
nested tx) green. 2 of 3 chains VMs.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev 3a21d0cf4a bridgevm: native ZAP struct-is-wire — kill node/vms/pcodecs dependency
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]>
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev 0b49832959 deps: bump evm v1.104.4 + precompile v0.19.1 (dex settle-only decomplect)
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]>
2026-07-12 16:44:17 -07:00
zeekay a93a588ab2 ci: run linux jobs on lux-build-amd64 ARC scale set (no GitHub-hosted builders) 2026-07-11 00:20:59 -07:00
zeekayandHanzo Dev 9861e9daab deps: consensus v1.25.35 -> v1.35.22 (fix pulsar import-path skew)
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]>
v1.7.2
2026-07-03 20:49:14 -07:00
zeekayandHanzo Dev 810fe38893 deps: require luxfi/threshold v1.12.1 (scheme/bls home)
Co-authored-by: Hanzo Dev <[email protected]>
v1.7.1
2026-07-03 15:59:50 -07:00
zeekayandHanzo Dev 38e66384f1 bridgevm: rip out vestigial BLS custody subsystem — custody is M-Chain
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]>
2026-07-03 13:57:33 -07:00
zeekayandHanzo Dev 844c0d5d71 bridgevm: remove trusted dealer from B-Chain custody keygen
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]>
2026-07-03 12:47:21 -07:00
zeekayandHanzo Dev 5c72db8860 quantumvm: enforce LP-0130 §6 — no user-payable blockspace on Q-Chain
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]>
2026-07-03 12:32:41 -07:00
zeekayandHanzo Dev bbce4b6975 chains: rename thresholdvm → mpcvm (LP-134 T-Chain retirement finished)
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]>
2026-07-03 01:22:30 -07:00
zeekayandHanzo Dev c2d052afc3 chains/thresholdvm: reconcile LLM.md naming to M/F/library (LP-134)
Co-authored-by: Hanzo Dev <[email protected]>
2026-07-03 01:05:24 -07:00
zeekayandHanzo Dev 0b704eded5 chains/thresholdvm: carve ThresholdService/MPCService/FHEService surfaces
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]>
2026-07-03 00:48:06 -07:00
zeekayandHanzo Dev 1c591e326c chains/thresholdvm: reconcile naming to LP-134/LP-7050 (kill teleportvm falsehood)
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]>
2026-07-03 00:42:35 -07:00
zeekay c63135e724 chore: gitignore stray plugin build artifact 2026-07-02 12:02:03 -07:00
zeekayandHanzo Dev 1676121436 fix(dexvm): exact-integer ZAP wire — match dex v1.14.0, kill cross-arch fork
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]>
v1.7.0
2026-07-02 10:40:05 -07:00
zeekayandHanzo Dev 3893cdfe3b test(dexvm): prove settlement conservation is independent of trust
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]>
v1.6.0
2026-07-01 20:53:15 -07:00
zeekayandHanzo Dev 76966c1b7a bump: latest dealerless PQ (corona v0.10.3, pulsar v1.8.0)
Co-authored-by: Hanzo Dev <[email protected]>
v1.5.0
2026-06-30 13:26:09 -07:00
zeekay a6359ae8a6 docs(brand): add hero banner 2026-06-28 20:35:28 -07:00
zeekay 079522eb32 chore(brand): dynamic hero banner 2026-06-28 20:35:27 -07:00
zeekay b190a322cb bump: dealerless pulsar v1.7.1 / corona v0.10.2 / dkg v0.3.5 / threshold v1.10.2
Go layer dealerless-clean (quasar/bridgevm/threshold protocols build; only the
separate wgpu GPU backend needs its native lib, unrelated to PQ).
2026-06-28 19:39:51 -07:00
zeekay 942e1bb200 deps: evm v1.99.48 -> v1.99.51 (block-production stall fix)
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.
v1.4.8
2026-06-28 00:56:03 -07:00
zeekay 3e8821a3d5 decomplect(warp): one warp (luxfi/warp v1.23.0) + one signed-message helper across bridgevm/zkvm/thresholdvm; deps evm v1.99.48 + precompile v0.16.0
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.
v1.4.7
2026-06-27 21:49:08 -07:00
zeekay 938a158076 fix(corona_general): label Corona as Module-LWE, not Ring-LWE
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.
2026-06-27 16:31:14 -07:00
zeekay 40d29ef0f6 fix(quantumvm): drop trusted-dealer dual-key keygen; build against consensus v1.25.35
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.
v1.4.6
2026-06-26 22:26:24 -07:00
zeekay 9361c09127 wip(chains): keyvm gas/fees/settlement/auth-only (in-flight checkpoint) 2026-06-26 19:10:13 -07:00
zeekay 8235dd1ce8 Merge graphvm genesis last-accepted block (fixes G-chain ZAP init: GetLastAccepted not implemented) v1.4.5 2026-06-26 15:53:06 -07:00
zeekay b50d044ba1 graphvm: resolve genesis as last-accepted block (fix G-Chain init)
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.
2026-06-26 15:39:05 -07:00
zeekay 71225a5a3d feat(dexvm): DEX fees settle to canonical LUX (network-independent) — L2/L3 ready
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.
v1.4.4
2026-06-26 15:30:05 -07:00
zeekay e19c11951a fix(dexvm): restore white-label universe deny-scan (refuse Liquidity/Liquid/partner)
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.
v1.4.3
2026-06-26 11:55:15 -07:00
zeekay e44493230e dexvm: dexcore → dex rename (dex/pkg/dexcore → dex/pkg/dex) 2026-06-25 23:15:44 -07:00