10 Commits
Author SHA1 Message Date
zeekay a93f6e3dec build the VMs without the daemon in their closure
A plugin is its own binary; the daemon that loads it has no business being
linked into it. Twelve VMs reached into github.com/luxfi/node for four things,
and each one now programs against a home that is actually its own:

- fee admission (Policy, FlatPolicy, NoUserTxPolicy, Validate) joins the
  settlement half already in chains/fee. One fee package, and the nodefee alias
  that marked the split is gone.
- the cross-chain artifact types move to chains/artifacts.
- the factory the daemon registers is luxfi/vm/manager.Factory, and the version
  types are luxfi/version. Both are ordinary modules the daemon reads too, so a
  VM imports them directly rather than through anything of ours.

The GPU switch is deleted, not moved. It was a global set during daemon startup
that a separate process could never observe, so every plugin read its default;
FHEOptions.Backend is the switch that was really there.

go list -deps names no luxfi/node for aivm, bridgevm, dexvm, evm, fhevm,
graphvm, identityvm, keyvm, mpcvm, quantumvm, schain or zkvm, and the built
plugin binaries carry no such string. oraclevm and relayvm still do: they are
re-export shims over luxfi/oracle and luxfi/relay, which build their fee
policies upstream. Their tests keep naming the daemon's fee package because
that is the type their value has.
2026-09-03 21:10:40 -07:00
zeekay c0d4022c71 Cite the specs by the numbers they carry
LP-134 was renumbered LP-1340 (lp-1340-lux-chain-topology); 21 citations
across 16 files still named the old number, which resolves to nothing.

Corona rests on Module-LWE, not Ring-LWE — LP-4805 says so in three places
and b17e086 already corrected one file, leaving evm/main.go and
schain/SECURITY_review.md claiming the wrong hardness assumption. The
two-family argument in LP-4900 depends on which lattice problem this is.

P3Q at 0x012205 is kind-byte signature dispatch; STARK/FRI is starkfri at
0x012220 (LP-7050). Naming P3Q for a scheme at another slot describes a
precompile that is not the one being imported.

Carries the substance of 2f22fa2, the one commit the parallel line held that
this history did not.
2026-09-01 01:40:34 -07:00
zeekay 87cb72352a schain: an authorization is spent once, and a block commits what it contains
An AllocateTx's nonce was covered by the owner's signature and compared to
nothing. Every gate it passed the first time passed again for the identical
bytes: the epoch was still current, the fingerprint still matched, the signer was
still the range owner, the ML-DSA signature still verified. Anyone who saw an
owner's allocate could resubmit it and consume the range's id space again, for
free, as often as they liked. A range now carries the nonce of the allocation
that last moved it, in the same key as its cursor, and takes only a strictly
greater one. The state root covers it, so a chain cannot disagree about what has
been spent and share a root.

Accept committed whatever was staged. Every apply starts by dropping the version
layer, so the layer belongs to whichever block was processed LAST — verify a
sibling between a block's Verify and its Accept and the commit carried the
sibling's writes under this block's id. Accept re-applies its own transactions
from committed state, commits, and only then advances the chain in memory, under
the lock it was mutating without.

Block position is now checked before block content: the parent must be a block
this node holds, the height must be parent+1, and the timestamp must not rewind
or run past a skew ceiling floored at the parent. None of it was checked, so a
block could claim any height — and the height it claimed became the chain's
height and part of every state root computed after it.

A proposer now leaves out what it cannot build rather than failing the whole
build: one unauthorized allocate anyone could submit used to drain the mempool
and destroy every manifest queued behind it. A verifier still refuses the whole
block, which is the half that carries the safety property.

Also: the height lookup reads an index instead of scanning the block map and
returning whichever entry map iteration reached first; abandoned blocks are
released; a rejected block leaves the index; boot no longer panics on a nil
logger when the manager supplies no runtime; and the empty-set check in the owner
gate is gone — pinning.Owner already answers that question, and answers the wider
one the length check missed.

Coverage 69.9% -> 97.9%, with 32 guards mutation-checked. The stale premise in
SECURITY_review.md is recorded at its head: it reviewed a checkout that did not
contain this VM.
2026-08-28 09:00:18 -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
zeekay 6552bd69cd 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 64163f8e02 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 1a7723684e 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/[email protected] 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 6c984d3862 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 929bf86938 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 0a20786095 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