93 Commits
Author SHA1 Message Date
zeekay 7581fd9d61 chore: sync working tree
Commits 2 outstanding change(s) that were sitting uncommitted.
No build artifacts and no secrets in the changeset (both checked).
2026-07-26 10:02:10 -07:00
zeekay abbfc19bd7 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 3fa525d1bd 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 <dev@hanzo.ai>
2026-07-25 15:09:22 -07:00
zeekayandHanzo Dev 4571c38a4a 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 <dev@hanzo.ai>
2026-07-25 14:45:13 -07:00
zeekayandHanzo Dev 54d89c8802 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 <dev@hanzo.ai>
2026-07-25 14:36:22 -07:00
zeekayandHanzo Dev d077aa4080 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 <dev@hanzo.ai>
2026-07-25 13:34:03 -07:00
zeekayandHanzo Dev 926d5030e9 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 <dev@hanzo.ai>
2026-07-25 13:20:09 -07:00
zeekayandHanzo Dev b58f770973 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 <dev@hanzo.ai>
2026-07-25 13:19:09 -07:00
zeekay 097cac64e3 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 b463e0f236 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 <dev@hanzo.ai>
2026-07-16 13:44:24 -07:00
zeekayandHanzo Dev d2478e11ff 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 <dev@hanzo.ai>
2026-07-16 11:01:36 -07:00
zeekay e5c48a9765 chore(deps): bump geth v1.20.1 + luxfi deps — stack unification 2026-07-15 11:17:08 -07:00
zeekayandHanzo Dev a1ac195dc8 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 <dev@hanzo.ai>
2026-07-14 15:55:09 -07:00
zeekayandHanzo Dev 02c90d5c88 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 <dev@hanzo.ai>
2026-07-14 15:39:27 -07:00
zeekayandHanzo Dev d47e04b4cb 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 <dev@hanzo.ai>
2026-07-14 15:35:52 -07:00
zeekayandHanzo Dev fd2073ecf3 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 <dev@hanzo.ai>
2026-07-14 15:35:09 -07:00
zeekayandHanzo Dev b7b7888f41 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 <dev@hanzo.ai>
2026-07-14 15:32:59 -07:00
zeekayandHanzo Dev 0f6fcca818 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 <dev@hanzo.ai>
2026-07-14 15:14:30 -07:00
zeekayandHanzo Dev e773c49c72 deps: pin node v1.36.4 (real tag, was wiped v1.30.6) + realign genproto
chains pinned github.com/luxfi/node@v1.30.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 <dev@hanzo.ai>
2026-07-12 21:36:03 -07:00
zeekayandHanzo Dev 20851569ad 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 <dev@hanzo.ai>
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev 0e1a174722 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 <dev@hanzo.ai>
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev a911385932 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 <dev@hanzo.ai>
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev dd8057d2f3 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 <dev@hanzo.ai>
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev 86d639be39 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 <dev@hanzo.ai>
2026-07-12 16:44:17 -07:00
zeekayandHanzo Dev 0bc0580d1b 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 <dev@hanzo.ai>
2026-07-03 20:49:14 -07:00
zeekayandHanzo Dev bdc88eb477 deps: require luxfi/threshold v1.12.1 (scheme/bls home)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-03 15:59:50 -07:00
zeekayandHanzo Dev 5b30b8b035 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 <dev@hanzo.ai>
2026-07-03 13:57:33 -07:00
zeekayandHanzo Dev 4858b3d72e 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 <dev@hanzo.ai>
2026-07-03 12:47:21 -07:00
zeekayandHanzo Dev 056576b2a9 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 <dev@hanzo.ai>
2026-07-03 12:32:41 -07:00
zeekayandHanzo Dev a7e8f6c065 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 <dev@hanzo.ai>
2026-07-03 01:22:30 -07:00
zeekayandHanzo Dev 28fef9c00e chains/thresholdvm: reconcile LLM.md naming to M/F/library (LP-134)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-03 01:05:24 -07:00
zeekayandHanzo Dev 3d2ee22d98 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 <dev@hanzo.ai>
2026-07-03 00:48:06 -07:00
zeekayandHanzo Dev c1d1868cf1 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 <dev@hanzo.ai>
2026-07-03 00:42:35 -07:00
zeekay c4c1c22035 chore: gitignore stray plugin build artifact 2026-07-02 12:02:03 -07:00
zeekayandHanzo Dev d92afe4f45 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 <dev@hanzo.ai>
2026-07-02 10:40:05 -07:00
zeekayandHanzo Dev a3d085d0d8 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 <dev@hanzo.ai>
2026-07-01 20:53:15 -07:00
zeekayandHanzo Dev 98f3fa0338 bump: latest dealerless PQ (corona v0.10.3, pulsar v1.8.0)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-06-30 13:26:09 -07:00
zeekay c92e3ece38 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 14426328d6 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.
2026-06-28 00:56:03 -07:00
zeekay f7e3f37ecb 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.
2026-06-27 21:49:08 -07:00
zeekay 1bc2a079a5 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 f0ec431ffd 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.
2026-06-26 22:26:24 -07:00
zeekay 0b440befd5 wip(chains): keyvm gas/fees/settlement/auth-only (in-flight checkpoint) 2026-06-26 19:10:13 -07:00
zeekay 8d5df3d304 Merge graphvm genesis last-accepted block (fixes G-chain ZAP init: GetLastAccepted not implemented) 2026-06-26 15:53:06 -07:00
zeekay 9c00994218 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 12eb748077 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.
2026-06-26 15:30:05 -07:00
zeekay 65f463f3b5 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.
2026-06-26 11:55:15 -07:00
zeekay 84d204ad69 dexvm: dexcore → dex rename (dex/pkg/dexcore → dex/pkg/dex) 2026-06-25 23:15:44 -07:00
zeekay cd98c25478 deps: fix consensus go.sum integrity break (re-tagged modules), bump consensus v1.25.29
The full chains build hit a go.sum SECURITY ERROR — luxfi/staking@v1.5.0 was
re-tagged (recorded hash no longer matched served bits), blocking everything that
pulls the consensus-adjacent module tree. Fixed PROPERLY, NO bypass
(GONOSUMCHECK/GOSUMDB=off never used):
- staking v1.5.0 → v1.5.1 (clean tag), records the real hash
- consensus v1.25.21 → v1.25.29 (was drifted behind; CROSS-REPO-VERSION-PIN), pulls
  crypto v1.19.26
- added luxfi/dex v1.5.20 require + recorded the transitive sums (shopspring/decimal
  et al) the evm/precompile dex wiring needs

Consensus path now builds + tests clean: schain + schain/cmd/plugin exit 0, full
./schain/... suite green. (Remaining full-tree ./... failure is the luxfi/evm+dex
CGO native link needing luxcpp prebuilt Metal/secp256k1 libs — a heavyweight-VM
native-build concern, NOT consensus and NOT in the schain path.)
2026-06-25 17:19:23 -07:00
zeekay a2e721de23 consensus parity: quantumvm joins the linear cert path; dexvm doc fix
quantumvm/vm.go: remove GetEngine() so the chain manager routes Q-Chain through
the linear consensuschain cert path (the same deterministic 2/3-stake BFT
certificate finality as C/D/P), not the undriven DAG engine. quantumvm already
implements the full chain.ChainVM/BlockBuilder surface; dropping GetEngine is the
whole change. Adds the _ chain.ChainVM + _ consensuschain.BlockBuilder asserts.

dexvm/factory.go: correct the stale 'Quasar consensus' comment — dexvm finalizes
via the linear cert path (Photon->Wave->Focus + 2/3-stake cert), same as C-Chain.
2026-06-25 16:07:07 -07:00
zeekay ab7d928d71 schain: Stage 2 — cryptographically verifiable signed pinned-writer (deletes raft's serialized writer)
The Stage-1 allocate gate was owner-gated in LOGIC (pinning.IsOwner over the
block's threaded proposer) but UNENFORCEABLE by a verifying node: a Lux block
carries no verifiable proposer, and resolving one (GetValidatorSet) can be a
network call, so the gate could not run inside the pure, local block apply. The
chain was therefore unsafe for >1 validator.

Make ownership self-attested and cryptographically verifiable:

- AllocateTx gains Signer/SignerScheme/SignerPubKey/Sig + Epoch/Nonce/Fingerprint.
  Sig is an ML-DSA signature (FIPS 204, the SAME scheme the validator NodeID
  derives from) over the SP 800-185-framed bytes Range|Count|Epoch|Nonce|
  Fingerprint. The owning proposer signs at BuildBlock with its staking key.

- Verify (applyAllocate) is pure + local: recompute owner = pinning.Owner(R,
  V@epoch); require Signer == owner; re-derive the NodeID from the carried public
  key (ids.DeriveMLDSA — the NodeID is a SHAKE256-384 commitment to the key) and
  require it to equal Signer; ML-DSA-verify the signature. Any failure rejects the
  whole block (fail closed).

- Validator-set locality: GetValidatorOutput carries no ML-DSA key, so Verify
  binds the key via the NodeID commitment instead of a set lookup, and uses the
  EpochFingerprint carried in the tx (recomputed from the local snapshot) as the
  DESIGN 6.4 guard — the network call (set resolution) happens in BuildBlock,
  never in Verify.

A non-owner's forged allocate is unverifiable by construction: it cannot sign as
the owner (no key), and claiming the owner's NodeID with a foreign key fails the
re-derivation. This is the property that replaces raft's serialized writer.

Tests (real ML-DSA keys, production NodeID derivation, no bypass): owner-signed
accepted + contiguous/monotonic; non-owner-with-VALID-key rejected; forged sig
rejected; foreign-key-for-owner-NodeID rejected; tampered tx rejected; epoch and
fingerprint mismatch rejected; verifier accepts owner-signed block regardless of
relay; state-root/manifest tests stay green. Full schain suite green under -race.
2026-06-25 15:47:37 -07:00
zeekay d8d5b7e07b schain: Stage 1 — leaderless pinned-writer allocator (AllocateTx + owner gate)
Replaces raft's serialization role for volume/fileId allocation with
owner-gated VM state riding native Lux consensus. The HRW owner of a range
is the only emitter of allocate mutations, enforced at block Verify — no
leader election.

- txs: AllocateTx{Range,Count} mirrors PutManifestTx codec (type byte + JSON);
  Verify rejects empty range / zero count. Owner gate is block-level, not in
  the tx (a tx cannot see the block's validator set).
- state: per-range counter alloc/<range> (GetAlloc/SetAlloc), folded into the
  SHAKE256 state Root() alongside manifests via rootPrefixes; domain bumped to
  SCHAIN_STATE_ROOT_V2 so the root covers allocator divergence.
- vm: applyAllocate increments alloc/<range> by Count, reserving [base,base+Count)
  — pure function of committed state, deterministic, version-layer only, no I/O.
  Owner gate: pinning.IsOwner(range, proposer, members) in ProcessBlock; a
  non-owner (or no-validator-set) allocate fails the whole block. ProcessBlock
  now Aborts staged state first so read-modify-write applies over COMMITTED
  state (idempotent across BuildBlock+Verify re-application).
- BlockContext{Members,Proposer,Epoch} threads the validator set + proposer as
  explicit deterministic inputs (no network I/O in Verify). BlockContextBuilder
  is the seam to the real consensus runtime (master-cutover stage); empty
  context = M0/no-allocate path, AllocateTx fails closed.

Tests (green, no go.sum bypass):
- owner allocates: contiguous + monotonic across blocks ([0,8) then [8,13)).
- non-owner rejected: proposer-side (BuildBlock) and verifier-side (Verify) →
  errNonOwnerAllocate; counter untouched; nothing leaks to durable base.
- empty validator set: fails closed (errNoValidatorSet).
- parallel distinct ranges: independent owners allocate independently.
- same range: serializes through its one owner.
- state root covers alloc: counter change moves Root(); Verify rejects a
  tampered claimed root (errStateRootMismatch).
2026-06-25 15:15:37 -07:00
zeekay 6b9ddc1f79 chore: drop stray aivm-gov-mcp build binary from v1.4.1, gitignore it
git add -A in the v1.4.1 commit swept in a 12.6MB local Mach-O build artifact.
Remove it from the tree and ignore it so binaries never land in the release again.
2026-06-25 14:43:29 -07:00
zeekay e6e9e0723c schain v1.4.1: state-root hardening + go.sum integrity fix + CI plugin path
CRYPTO (Red/cryptographer HIGH): manifest state root was a hand-rolled
len||k||len||v SHA-256 fold (length-extendable, no domain sep). Now SHAKE256 +
SP 800-185 leftEncode framing + 'SCHAIN_STATE_ROOT_V1' domain, mirroring the
validator NodeID scheme (ids/node_id_scheme.go) — not length-extendable,
domain-separated, PQ-strength. Full schain suite green (determinism +
change-sensitivity + Verify-rejects-tampered-root), NO sum bypass.

DEPS (integrity, NOT bypassed): luxfi/zap@v0.8.10 had been re-tagged (go.sum
recorded an immutable-tag hash that no longer matched the served bits). Fixed
PROPERLY by moving to the clean v0.8.11 and recording the real hashes; added the
private hanzos3 org to GOPRIVATE (it was missing) so luxfi/zapdb's hanzos3/go-sdk
dep resolves via git+go.sum, not the public sumdb. go.sum is now correct, not skipped.

CI: registered schain in .github/images.json (build matrix); added the canonical
schain/cmd/plugin/main.go (ZAP-native vm/rpc.Serve, no build tag — the path the
release workflow builds), removing M0's grpc-tagged rpcchainvm plugin. Plugin
builds clean via ./schain/cmd/plugin.
2026-06-25 14:42:37 -07:00
zeekay fc664d376c schain: HRW pinning primitive + leaderless pinned-writer design
Pure weighted-rendezvous (HRW) owner assignment over the P-Chain validator set
(schain/pinning): Owner/IsOwner/EpochFingerprint — single-writer-per-range as a
function of (range, validatorSet@pChainHeight), no leader election, parallel across
ranges, same-object double-write impossible. 9 tests green; nothing wired yet (safe).
DESIGN_pinned_writer.md: the spec to replace the s3 master's raft (the last gRPC
server). v1 on linear ChainVM (already leaderless). TWO HARD blockers gate cutover:
(1) off-chain blob durability must be ordered before the on-chain manifest commit;
(2) validator-set lookup in Verify must be a pure local read (determinism). Staged:
AllocateTx → shadow mode (after blockers) → kill raft.
2026-06-25 13:29:53 -07:00
zeekay b5faf6683d schain: M1 — manifest state root (multi-validator safety) + S3 object split
PART A — manifest STATE ROOT (the >1-validator prerequisite M0 omitted):
- state.Root() hashes the committed manifest keyspace deterministically via the
  zapdb prefix iterator NewIteratorWithStartAndPrefix(nil, prefixManifest),
  folding each entry length-prefixed in lexicographic key order. Mirrors
  dexvm/state/state.go:395 StateHash, narrowed to the one keyspace.
- VM.computeStateRoot folds blockHash+height with state.Root() (dexvm/vm.go:1260);
  ProcessBlock stamps BlockResult.StateRoot (dexvm/vm.go:584).
- The root travels in the block HEADER (new 32B field after parentID; block id
  commits to it). Block.Verify recomputes it and REJECTS a mismatch
  (errStateRootMismatch) + Aborts staged writes — the safety gate dexvm computed
  but never compared.
- Tests: determinism across write order, change-sensitivity (object set / etag /
  size / fileIds), Verify-rejects-tampered-root.

PART B — S3 OBJECT path, on-chain-metadata / off-chain-blob split, end to end
(schain/object/):
- Store.PutObject streams the blob to a Volume (OFF chain) -> fid, then commits
  only PutManifest{bucket,object,[fid],size,etag} to the VM through a real block
  (ON chain). GetObject reads the manifest back and reconstructs from the volume.
- object.Volume is the seam; MemVolume satisfies it for M1 with a fid shape
  mirroring hanzo/s3 needle.FileId and etag = base64(md5(blob)) (= ContentMd5).
- M2 PLUG POINT (object/volume.go): swap MemVolume for a github.com/hanzoai/s3
  s3/operation.SubmitFiles adapter; nothing in the VM or object.Store changes.
- Test proves blob bytes appear in NO accepted block, the block stays
  manifest-small, the manifest IS on chain, and GET byte-reconstructs.

luxfi packages only; zapdb-native; no protobuf in state; SDKROOT needed on macOS.
2026-06-25 13:28:14 -07:00
zeekay 738e8f03c3 schain: M0 storage-chain VM skeleton (manifest → zapdb through a real block)
Fork dexvm's commit discipline into a new storage VM (VMID 'schain'): dual-DB
(durable zapdb base + per-block versiondb), pure-deterministic ProcessBlock, ONE
CommitBatch at Accept (mirrors dexvm/vm.go:1194, block.go:159). M0 op: PutManifest
{bucket,object,fileIds,size,etag} staged in the version layer, durable only after
Accept; GetManifest reads committed state. Proves the on-chain-metadata / off-chain-
blob architecture for an embedded decentralized S3 'S-chain'.

schain_test.go (real on-disk zapdb.New): manifest absent in base DB before Accept,
present after — versiondb→CommitBatch proven end-to-end; +Reject-aborts-staged and
tx-codec round-trip. go vet + -race clean.

M1 notes (in-code): add a manifest STATE ROOT before multi-validator consensus
(dexvm computeStateRoot pattern); keep any future cross-chain blob-ref leg separate
from the manifest commit; add genesis/config. Next: wire the S3 Sig-V4 gateway
(PUT→needle off-chain + PutManifest on-chain; GET→manifest+needle stream).
2026-06-25 12:44:03 -07:00
zeekay a56f476cd5 decomplect: drop redundant brand denylist from dexvm/registry
forbiddenChainTokens + IsForbiddenChainRef named the off-network white-label
universe by string to reject it. That negative gate is redundant: the
positive gate (ChainVerifier) already admits only real on-chain Lux assets,
so an off-network universe can never pass regardless. Removed the brand list
and the func; kept the genuinely-distinct guards (mock/synthetic liquidity,
ASCII-ticker-as-id). AssertNoForbiddenAssetRefs keeps its signature for the
gate's call shape. No brand strings remain; builds clean.
2026-06-24 11:10:32 -07:00
zeekay b8de7edf38 deps: bump precompile v0.5.59 + consensus v1.25.21 + evm v1.99.39 (no stragglers) 2026-06-23 09:28:09 -07:00
zeekay 475a4d0c6f deps: go mod tidy after merging consensus v1.25.19 2026-06-23 01:05:24 -07:00
zeekay f3c60ab9f0 merge: origin/main (consensus v1.25.19 Round1 fix, b8f7e05) into dex-real-assets-registry 2026-06-23 01:04:04 -07:00
zeekay 58d4a8d4f3 dexvm: Gate-A real-assets-only startup enforcement + LABELED rename
VM.Initialize now runs enforceRealAssetsOnly before marking initialized:
fail-closed over the consensus-supplied network identity (never operator-
spoofable). Refuses synthetic flags on a value net, value activation with no
legal consensus mode, a wrong-net/Liquidity manifest, value with no real
assets, bad allowed-kinds. Config gains the real-assets fields (manifest
path + SHA-256 pin, synthetic flags, consensus mode, caps/halt). Renames
HONEST_VALIDATOR_LAUNCH -> HONEST_VALIDATOR_LABELED across the registry
(value qualified by what it IS — labeled CFT parity — not when it runs).
Adds cmd/dex-assets-validate (CI proves every manifest entry real against
the target net RPC) + the validate-asset-manifests workflow. dexvm tests +
registry gate tests green.
2026-06-23 01:03:49 -07:00
zeekay f8f09e07af registry(dexvm): golden KAT — AssetID byte-parity with dexcore (MED-1)
Mirror the canonical cross-home golden KAT: registry.DeriveAssetID must reproduce
the EXACT 32-byte AssetID bytes that luxfi/dex pkg/dexcore.DeriveAssetID asserts
(AssetIDGoldenVectors). Fixed (networkID=2, source chain all-0x11, {ERC20, EVM_NATIVE,
UTXO}) → expected id vectors, byte-identical to the dexcore side. This locks the
resolve↔register equivalence the 0x9999 value path depends on: a registered AssetID
(here) and a swap-derived AssetID (dexcore, via the precompile resolver) name the SAME
asset by the SAME id. A drift in either home's preimage discipline now fails CI in BOTH
suites — a real consensus-identity divergence caught in test, never in prod.

Verified: CGO_ENABLED=0 go test ./dexvm/registry -run GoldenKAT green; the same three
vectors pass in dex/pkg/dexcore.
2026-06-22 22:51:00 -07:00
zeekay c77bc9c925 feat(dex/registry): embed per-network asset manifests + localnet native builder
Make the per-network asset manifests (mainnet/testnet/devnet) available to a node
binary with no filesystem dependency, so the EVM plugin can build the 0x9999
value-path AssetResolver from the CI-approved, content-hashed asset set compiled
into the binary. Selection is by EVM chainID (96369/96368/96370). Localnet (1337),
whose C-Chain id is environment-specific, gets a runtime-rooted native-only manifest
(LocalnetNativeManifest) synthesised from the node's live cChainID — never a constant.

- embedded.go: //go:embed the three manifests; EmbeddedManifestFor(evmChainID) +
  LocalnetNativeManifest(networkID, cChainID).
- manifest.go: split decodeManifestBytes out of loadManifestBytes so the embed path
  and the disk path share one decoder (shape-validate + content-hash + unknown-field
  rejection identical for both).
2026-06-22 20:56:23 -07:00
zeekay 40fa820c00 revert(dexvm): drop C<->D keeper RPC seam (dex.submitTx + dex.getSettlement)
The keeper/venue model is abandoned. The D-Chain (dexvm) trades via its own
consensus-driven state transition (matcher-at-Verify), not via an off-chain
keeper polling dex.getSettlement to drive 0x9999. Revert the three v1.3.16
commits' additions to the v1.3.15 working state:

  - vm.go:           remove GetSettlement + the proceeds-coordinate record
  - chainvm.go:      remove the SubmitTx RPC seam additions
  - api/service.go:  remove dex.submitTx + dex.getSettlement handlers
  - state/state.go:  remove PutSettlement/GetSettlement settlement index
  - api/submit_test.go, state/settlement_test.go: remove keeper-RPC tests

Deps are unchanged from v1.3.15 (go.mod/go.sum identical). bridgevm + dexvm
build clean and all tests pass.
2026-06-22 03:11:57 -07:00
zeekay ed70166f65 merge: dexvm C<->D keeper RPC seam (dex.submitTx + dex.getSettlement)
Exposes the two RPCs the off-chain dexkeeper drives the 0x9999 two-phase atomic
DEX seam with:
  - dex.submitTx: mempool entry so the keeper can inject ImportTx (consume the
    C->D atomic object) + the settling RelayOrderTx (clob_submit, CollateralRef-bound)
  - dex.getSettlement(collateralRef): returns the D->C proceeds object's
    (outputID, amount) recorded deterministically in settleFromFills, the Phase-B input

State.Put/GetSettlement write through versiondb (commit-atomic, consensus-safe).
RelayOrderTx.Verify permits an unsigned relay (authority binds to the escrow owner,
not From). dexvm/api + dexvm/state tests pass.
2026-06-22 02:43:37 -07:00
zeekay a4518c67db deps: consume evm v1.99.36 + precompile v0.5.56 (dexvm swap-seam parity) 2026-06-21 22:48:26 -07:00
zeekay a58077e5f5 deps: bump vm v1.2.5 + api v1.0.15 (UTXOAssetID rename — fixes LuxAssetID build break, latest patch) 2026-06-21 22:44:55 -07:00
zeekay d74800a7fd deps: converge threshold v1.9.9 + crypto v1.19.21 + database v1.20.3 + geth v1.17.12 + warp v1.19.5 + metric v1.5.9 + proto v1.3.5 (latest patch within v1.x.x) 2026-06-21 22:41:07 -07:00
zeekay 9cfce3954a dexvm: pin C<->D atomic seam wire (69-byte, spent witness) against precompile golden
The swap-seam HIGH fix (c242e01) extended encodeExportedOutput to 69 bytes
(rail|owner|asset|amount|spent), where the trailing spent witness carries the matched
INPUT amount on a D->C swap proceeds leg so the C-side ImportSettlement can enforce the
taker's recorded slippage limit. That widening MUST be byte-identical with the precompile's
exportedOutputSize9999 or D would export an object C cannot decode (every swap settlement
reverts ErrNativeSettleMalformed, no taker is ever credited).

native_seam_parity_test pins the lockstep on the D side: a hardcoded golden vector
(reproduced byte-for-byte by precompile/dex/native_seam_parity_test) + a round-trip that
proves the spent witness survives + a wrong-width rejection. If either repo's layout drifts,
one of the twin parity tests fails BEFORE the bytes reach consensus. Closes the dangling
'native_seam_parity_test pins it' reference in atomic.go.
2026-06-21 22:41:07 -07:00
zeekay b4bca041c1 dexvm: plumb fill-attestation pubkey through genesis + carry matched-spent on proceeds (swap-seam)
HIGH (inert attestation gate): FillAttestationPubKey was read by settleCarried but
NEVER written — parseConfig copied only 10 enumerated fields and parseGenesis carried
only DexZapEndpoint+TrustedChains. So in prod the pubkey was nil, verifyFillAttestation
returned nil, trustCarried was always true, and the fabricated-fills guard could never
engage: a malicious/MITM proposer could settle fabricated fills and drain a cross-taker's
seam reserve via the proceeds leg. Fix: add FillAttestationPubKey (hex) to the Genesis
struct + parseGenesis, assigned into vm.Config. It is GENESIS-pinned only — deliberately
absent from parseConfig — because it is consensus-affecting (gates trustCarried ->
escrow-consume + export legs -> computeStateRoot); a per-node value would fork the
StateRoot. A wrong-width key is a FATAL genesis error (fail-closed). FillAttestationSeed
stays json:"-" (KMS-only, never in a genesis/config blob).

MEDIUM (consensus comment): config.go now states the stronger invariant — the pubkey is
STRICTER than DexZapEndpoint (which is node-local transport, not recomputed in the
deterministic settle path) and MUST be a single genesis-pinned constant.

MEDIUM (taker-authenticated MEV floor): the proceeds export object now carries the matched
input (AtomicOutput.Spent; shared-memory object 61->69 bytes, both repos in lockstep) so
the C-side ImportSettlement can enforce the taker's OWN recorded slippage limit
(proceeds >= spent*worstRate) independently of the keeper's relay-supplied priceLimit. A
keeper that zeroes the relay limit to sandwich the taker still produces a proceeds object
C will refuse; understating spent loosens the floor but inflates refund, which the
per-taker principal cap + seam-reserve conservation independently bound.

Tests (redteam, CGO_ENABLED=0 pure Go): redteam_genesis_attestation_test drives a REAL
genesis blob through Initialize->parseGenesis (NOT a direct Config write) and proves the
pubkey reaches Config, a fabricated/unattested block becomes a full refund by virtue of
genesis alone, a wrong-width genesis key fails Initialize closed, and the runtime config
blob cannot set the consensus-affecting pubkey. native_seam canary pins the joint 69-byte
object wire (incl. spent) across both repos. Full dexvm redteam + unit suites green.
2026-06-21 22:41:07 -07:00
zeekay 99e41b38c1 dexvm: close HIGH-1 proceeds-unclaimable + MEV floor + fill attestation (swap-seam)
HIGH-1 (THE happy-path break, swapsWork=NO): settleFromFills exported the taker's
PROCEEDS leg under assetFromRef(ref,leg)=SHA256(ref||leg), a synthetic routing handle.
The C-side ImportSettlement requires recAsset==assetID(currency_out) (the injective id
that also keys seamReserve[assetOut]); a SHA256 handle never matched, so the proceeds
credit ALWAYS reverted (ErrNativeSettleAsset) and the taker never received their swap
output. FIX: settleFromFills exports the proceeds leg under the REAL output-asset id
carried with the settling relay (RelayOrderTx.AssetOut, signature-bound, keeper-asserted
and C-bound => a wrong value is taker-liveness-only, never theft/mint). Removed the dead
assetFromRef SHA256 helper. Kept the prior pass's owner-bind + one-time-consume +
per-intent binding intact.

MEDIUM (bounded sandwich/MEV): settleFromFills now enforces a worst-acceptable CLOB
price carried from the relay (RelayOrderTx.PriceLimit/LimitIsUpper, derived from the V4
SqrtPriceLimitX96): a fill worse than the limit is refused (BUY ceiling, SELL floor;
0=no limit), leaving the escrow reclaimable.

MEDIUM (single-proposer fabricated fills): populate+verify the reserved carried-fills
attestation. The venue signs blake3(domain||blockHash||entries) with Ed25519; every
validator verifies it before settling (verifyFillAttestation), gated behind
Config.FillAttestationPubKey (default-on once set; fail-secure full-refund when a block's
fills are unattested/forged). Config note: the pubkey MUST be identical across validators
(consensus-affecting, same class as DexZapEndpoint).

Tests (CGO_ENABLED=0, default + redteam): TestRED_SwapProceeds_* (unit + e2e happy-path
credit under the real output asset), TestRED_SwapSlippageLimit_*, TestRED_FillAttestation_*
(verify/forge/fabricated-refund/off-by-default). Full dexvm suite green.

Extends fix/swap-rail-escrow-owner-bind (f173650). Within chains v1.3.15.
2026-06-21 22:41:07 -07:00
zeekay 2b3d103098 deps: bump evm v1.99.31 + precompile v0.5.51 for DEX native-atomic-seam release (v1.3.14) 2026-06-21 07:19:36 -07:00
zeekay ffc7d83db8 dexvm: rail-tag the C<->D atomic object (D side of Red H1 fix)
The D side of the precompile/dex H1 rail-binding. The shared-memory object wire
goes 60->61 bytes: rail(1)|owner(20)|asset(32)|amount(8), byte-identical to the
precompile's exportedOutputSize9999. The rail tag is the lane the value travels
(RailSwap=0 default / RailLP=1) and round-trips through the atomic core unchanged.

- txs.AtomicOutput gains Rail; ImportTx/ExportTx.Verify enforce one-rail-per-leg
  (structural half, mirroring the asset bind).
- encode/decodeExportedOutput carry the rail byte (decode now returns rail too).
- executeImport binds the credited outputs' rail to the consumed UTXO's RECORDED
  rail (authoritative half; errImportMixedRails), so an import cannot re-lane
  value — a swap object onto the LP lane or vice-versa.
- executeWithdraw (the LP collect/withdraw export) stamps RailLP; the fill/refund
  settlement exports (settleFromFills) stay RailSwap (zero default).

New: TestNativeRail_ImportRejectsRailMismatch (both directions) +
TestNativeRail_ImportAcceptsMatchingRail (positive control). Wire-parity canary
TestNativeSeam_WireMatchesPrecompile asserts byte-identity on BOTH rails. LP-seam
round-trip + conservation/redteam offset helpers updated to the 61-byte wire.

CGO_ENABLED=0 GOWORK=off go test ./dexvm/... green.
2026-06-21 02:59:34 -07:00
zeekay 6ce50cf7bd dexvm: LP D-committed seam round-trip tests (commit import / collect export)
Prove the D side of the LP rail (precompile/dex 0x9999 modifyLiquidity /
collectPosition) consumes/produces the EXACT atomic wire WITHOUT any new D-side
primitive: a position-commit object (DL01) is byte-identical to any value object,
so the C->D commit leg == executeDeposit (atomic import -> CLOB ledger credit; the
position's collateral + fee accrual lives in the CLOB, funded by the consumed
object) and the D->C collect leg == executeWithdraw (debit realized balance ->
atomic export). Three tests: commit-funds-position (+ replay reject),
collect-exports-principal+fees, full round-trip conserves.
2026-06-21 02:15:54 -07:00
zeekay e38db0d1ae dexvm: native C<->D atomic seam round-trip tests
Prove the dexvm import/export wire is byte-identical to the precompile's atomic
object (TestNativeSeam_WireMatchesPrecompile), that a C->D object the precompile
PUT imports cleanly on D, that a D->C export the dexvm writes decodes as the
precompile's settlement object, and the full C->D->C round trip. Locks the
cross-chain seam compatibility the 0x9999 money path depends on.
2026-06-21 00:19:25 -07:00
zeekay a256a015b9 deps: evm v1.99.30 + precompile v0.5.50 + clean go.sum
Bump to the clean-go.sum builds of evm/precompile and regenerate chains' go.sum
from a clean module cache (v1.3.12's consensus/geth hashes were poisoned by a
tampered local cache, breaking clean-CI plugin builds). No chains code change.
go mod verify clean; build + bridgevm + dexvm tests green.
2026-06-20 22:13:29 -07:00
zeekay e2a16b4aaa deps: evm v1.99.29 + precompile v0.5.49 (hardened 0x9999 receipt-settlement)
Bump github.com/luxfi/evm v1.99.28 -> v1.99.29 (removes deprecated DexZap
live-backend wiring) and github.com/luxfi/precompile v0.5.47 -> v0.5.49
(complete, red-swarm-hardened 0x9999 V4 receipt-settlement surface). dexvm
remains the D-Chain matcher/receipt producer; the C-Chain settles inline.
2026-06-20 15:02:05 -07:00
zeekay 02c86dd097 merge: reconcile docs (582fc44) onto the v1.3.11 bridgevm BLS-scheme fix
origin/main had drifted to a docs-only commit that did not include the
bridgevm BLS threshold-scheme registration (v1.3.11, 4ed520a) — without it the
v1.30.16 bridge regression recurs. Merge keeps both.
2026-06-20 15:00:52 -07:00
zeekay 4ed520a52c fix(bridgevm): register BLS threshold scheme in the plugin process
bridgevm runs as a standalone rpcchainvm plugin; mpc.go called
threshold.GetScheme(SchemeBLS) but never imported crypto/threshold/bls,
whose init() registers it. The full node binary pulled it in via
platformvm, but the v1.30.16 plugin build didn't -> B-Chain failed VM
init ('threshold: scheme not registered'). Blank-import to self-register.
2026-06-19 20:12:33 -07:00
zeekay 015ae16d6f deps: pin geth v1.17.11 + consensus v1.25.18 + evm v1.99.28 + precompile v0.5.47 (real semver); MVS-propagate transitive bumps 2026-06-19 14:44:18 -07:00
zeekay 53b01ecf64 Merge feature/zchain-rollup-starkfri-pq: zkvm Z-Chain rollup + starkfri PQ 2026-06-19 14:26:02 -07:00
zeekay e462b8cb86 chains/zkvm: wire Z-Chain strict-PQ from one config bit (Red H1 wiring + LOW)
The Z-Chain is DEFINITIVELY strict-PQ (shielded settlement on the canonical
Lux strict-PQ profile), but the switches were inert: the default ZConfig
omitted StrictPQ (->false) and RegisterZKPrecompiles had ZERO callers.

Task 1 — drive BOTH switches from the single ZConfig.StrictPQ field:
- Default ZConfig now pins StrictPQ=true + ProofSystem='stark' (the only
  system accepted under strict-PQ). A genesis with no explicit ZConfig
  yields a strict-PQ chain; a permissive deployment MUST set StrictPQ=false
  explicitly.
- VM.Initialize now calls precompiles.RegisterZKPrecompiles(reg, config.
  StrictPQ) — same bit. On strict-PQ the classical Groth16 (0x80) / PLONK
  (0x81) verifiers are NOT registered (fail-closed by absence); only
  STARK/FRI (0x82) + the fail-closed stubs exist. Added VM.ZKPrecompiles()
  / VM.StrictPQ() accessors.

LOW (Red) — TestStrictPQ_RealBN254VKLoadErrors drove a test-local mirror
(reapplyStrictPQVKGuard), not the production loadVerifyingKeys. Added a
config path (ZConfig.VerifyingKeys) to supply real keys in-memory; the test
now drives the REAL NewProofVerifier->loadVerifyingKeys and asserts the
strict-PQ real-bn254-VK guard fires there. Deleted the mirror.

Tests (a)(c): default Z-Chain is strict-PQ + registers strictPQ=true;
classical 0x80/0x81 absent, 0x82 present; a groth16 shielded proof is
REFUSED before reaching bn254; explicit StrictPQ=false flips both switches
in lockstep (profile-driven, not hardcoded); real-VK guard via the
production loader; non-strict chain still accepts a real bn254 VK.
2026-06-19 13:31:46 -07:00
zeekay 150ca97407 chains(zkvm): hard-disable classical shielded/ZK paths on strict-PQ (Red H1/H2/H4)
H1 — shielded-tx ProofVerifier was classical-only (groth16/plonk) and
fail-closed only via all-zero dummy VKs; real bn254 VKs would re-enable a
CRQC-forgeable shield/unshield mint/steal path. Add ZConfig.StrictPQ and a
single gate refuseClassicalUnderStrictPQ(proofType) called at the top of
VerifyTransactionProof AND per-tx in the GPU batch path (which verifies
groth16 inline, bypassing VerifyTransactionProof). Under strict-PQ only
"stark" is accepted — wired to precompile/starkfri (cSHAKE256/Goldilocks
FRI, fail-closed until the prover binds), public inputs bound to the tx's
nullifiers+commitments. loadVerifyingKeys now ERRORS on a non-dummy bn254
VK under strict-PQ (explicit fail-closed, not implicit dummy-keys). The
remaining prover work (p3q_prove C ABI + shielded/ML-DSA AIR) is tracked
as a code comment; shielded value is effectively disabled on strict-PQ
chains until it lands — the correct no-classical-fallback posture.

H2 — Groth16/PLONK precompiles (0x80/0x81) were registered unconditionally
and Run([]byte) has no AccessibleState to call RefuseUnderStrictPQ.
RegisterZKPrecompiles(registry, strictPQ) now omits Groth16/PLONK on
strict-PQ chains (registering STARK 0x82 + fail-closed Halo2/Nova stubs
only) so 0x80/0x81 fail closed by absence; non-strict chains keep them as
an optional building block (kept, not deleted). CrossChainZKVerifier gains
StrictPQ that refuses routing Groth16/PLONK while still routing STARK.

H4 — verifyPLONK computed a self-cancelling pairing, discarded the result,
ignored public inputs, and returned valid for ANY >=544-byte blob (total
bypass). Now fails closed (errPLONKVerifierIncomplete) after structural
parsing — NEVER universal-accepts.

Groth16/bn254/PLONK remain present-but-gated; STARK/FRI (P3Q) is the only
accepted proof path on strict-PQ chains.

Tests: strict-PQ refuses groth16/plonk/bulletproofs (direct + GPU-batch);
real-VK-load errors; STARK fails-closed unbound and accepts bound with
nullifier+commitment-bound public inputs; non-strict path unchanged;
strict registry omits classical + keeps STARK; cross-chain strict refuses
classical; PLONK never universal-accepts. H-02 regression updated to the
new wired-STARK fail-closed behavior.
2026-06-19 12:48:26 -07:00
zeekay 57aed35010 chains/zkvm: Z-Chain rollup proof verifier switched Groth16 -> STARK/FRI (P3Q)
Make the Z-Chain MLDSA-rollup verification path post-quantum. The
rollup aggregates per-validator ML-DSA-65 sigs over the round digest;
the proof was classical Groth16/bn254 (pairing-based, quantum-breakable)
and is now a strict-PQ STARK/FRI proof (P3Q: Plonky3 fork, cSHAKE256
Merkle over Goldilocks, FRI; no KZG, no pairings, no trusted setup).

VERIFIER (the clean half, done):
- precompiles/zk_verifiers.go: STARKVerifier stub -> delegates to
  precompile/starkfri.Verify. Parses [proof_len][proof][pub_len][pub];
  proof must carry the 'P3Q1' magic. FAILS CLOSED (errVerifierUnavailable,
  never resultValid) when no FRI binding is registered (CGO_ENABLED=0 /
  no -tags starkfri_p3q) — no forgery oracle. The classical
  Groth16Verifier/PLONKVerifier are KEPT intact at their own addresses.
- precompiles/crosschain.go: route VerifierTypeSTARK to STARKVerifierAddr
  (was errNotImplemented).
- tests: STARK now proven to delegate to starkfri (accept->0x01,
  reject->0x00, bad-magic rejected before callback, unbound fails closed)
  and to route cross-chain; Groth16/PLONK tests unchanged.

PROVER (the honest remaining step, FLAGGED — NOT faked):
- zwitness.go: MLDSAGroth16 -> MLDSAStark rename; doc now states the
  proof is STARK/FRI. Body stays ErrZWitnessNotImplemented: ~/work/lux/p3q
  exposes a Rust prover but NO p3q_prove C ABI, and has NO MLDSA-rollup
  AIR. Emitting fake proof bytes on the finality path would be a forgery
  oracle, so the prover binding is left as the outstanding step.

go build ./zkvm/... + go test ./zkvm/precompiles/ green (CGO_ENABLED=0,
GOWORK=off). Real cgo-tagged starkfri binding (-tags starkfri_p3q)
compiles + KAT passes against the in-tree p3q staticlib.
2026-06-19 12:23:44 -07:00
zeekay 103f5e2d17 dexvm: bind import credit to consumed-UTXO asset/owner + custody hardening
executeImport reads the consumed UTXO owner|asset|amount from shared memory and binds the credit to it (rejects declared!=actual; native-aliasing closed); two-pass validate-then-consume so a rejected import mutates nothing. Withdraw-rail consume-once on fillRef.
2026-06-19 11:34:41 -07:00
zeekay d0c0fdfaaf chains/dexvm: atomic custody rail (custody.go) — deposit=import+credit, withdraw=debit+export
executeDeposit = executeImport (atomic C/X debit, consume-once) + clob_deposit relay (credit
D-Chain balance; refuses credited!=imported). executeWithdraw = clob_withdraw (debit, realized
clamped) + executeExport (credit exactly realized; refuses realized>want). Reuses the
conservation-tested ImportTx/ExportTx rail. custody_conservation_test: real atomic.SharedMemory,
deposit→trade→withdraw conserves C in==out, UTXO consumed once. NOTE: not yet wired into the
carried-fills BuildBlock plan (network-upgrade-gated follow-up, like #9).
2026-06-17 21:10:33 -07:00
zeekay b27c44bfc8 Merge dexvm-atomic-proxy: stateless atomic ZAP proxy + #9 carried-fills consensus fix 2026-06-17 17:12:55 -07:00
zeekay d649804f4a chains/dexvm: #9 consensus-fork fix — proposer-relays-once + carry-fills-in-block
Decomplect obtain-fills (proposer, BuildBlock, once network-wide) from settle-from-carried-
fills (deterministic, Accept). No validator relays at Verify/Accept -> block output is a pure
fn of (height, carried ts, tx bytes, carried fills) -> byte-identical StateRoot. NEW
carried_fills.go wire section (block + vertex) with a RESERVED fill-signature field for the
future trustless path (no 2nd wire bump). #7 computeStateRoot completed (commits state+atomic).
TestRED_PerValidatorRelay_SplitsConsensus PASS (0 cross-validator relay); redteam 21/0 (37/0);
conservation/determinism green; race-clean. NETWORK-UPGRADE-GATED wire-format change.
2026-06-17 17:12:55 -07:00
zeekay fe433084c7 chains/dexvm: rip out matcher+state -> stateless atomic ZAP proxy
orderbook/perpetuals/lending/mev dirs + backend.go deleted; vm.go 2230->830 lines.
NEW atomic.go (shared-memory import/export at Block.Accept) + relay.go (rpc.ZAPDial,
frame byte-identical to zapwire, no luxfi/dex dep). State holds only nonce:/receipt:/
consumed: -- no order/pool/position keys. txs reduced to import/export/relay. NEW
conservation_test.go::TestEndToEndAtomicValueConservation. Verified -tags dchain build+tests
green -race; grep matchAllOrders|tryMatch|crossOrders empty.
2026-06-17 13:31:58 -07:00
zeekay 0b69f01dfc deps: update to latest real-semver, drop local replaces, fix breaks 2026-06-11 09:09:40 -07:00
zeekay c8f76d6f76 scrub Liquidity branding + corona→corona (OSS brand hygiene) 2026-06-10 23:44:07 -07:00
zeekay c97ae29ea0 ci: route to canonical native arcd labels [self-hosted, linux, <arch>]
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
2026-06-10 20:18:09 -07:00