chains/rust/dexvm is the Go registry at chains/dexvm/registry ported: what an asset IS, what a market IS, which kinds may be registered, and whether native value may activate. All 35 D vectors now agree with Go and C++ on every compared field, and on every note besides — the port raises the reference's own sentinels, so its sentences come out byte-identical down to where the 160-byte trim cuts. D is not a block chain and neither is the reference: Go's dexvm has no block, no transaction and no VM, so there is no seam here and no lux-node dependency. One crate dependency, sha2, because an AssetID is a length-prefixed SHA-256 fold and those bytes are consensus. A refusal's identity and its sentence are kept apart, because Go's classify unwraps to the sentinel before it reads any words and the wrapped text of one refusal ends "UTXO assetID must be 32 bytes" — a classifier reading the sentence calls a malformed reference a missing ledger entry. The Q evaluator now answers the identity op from the two numbers it derives ids under, and runs Block::on_chain before Block::well_formed, which is the order Qvm::verify and the reference's Block.Verify both use. Q_CHAIN_IDENTITY, Q_BLOCK_FOREIGN_CHAIN and Q_BLOCK_FOREIGN_NETWORK disagreed on seven compared fields between them; all three agree now. Both evaluators derive a verdict class through one copy of the shared word table rather than setting one by hand. Both crates grew a tests/corpus.rs that runs the evaluator over the shared corpus from cargo test, so a moved verdict fails in the crate that moved it rather than in a target that builds nine evaluators first. 56 tests in dexvm, 126 in quantumvm. Co-authored-by: Hanzo Dev <[email protected]>
6.7 KiB
lux-quantumvm — the Lux Q-Chain in Rust
The post-quantum lane of the parallel-witness finality model (LP-020). Ported
from the Go reference at ~/work/lux/chains/quantumvm (6,470 lines), against
the object-safe VM seam of ~/work/lux-rs/node (src/vm.rs).
6,875 lines across 14 files. 126 tests, all green. No stubs.
Build
PATH=~/.cargo/bin:$PATH cargo test
PATH=~/.cargo/bin:$PATH cargo build --release --bin conformance
The one thing this crate needs that the X-chain does not is libluxcrypto: the
ML-DSA-65 signature is FIPS 204 from lux-pq, the same library the Go node
signs under, reached by path. build.rs repeats the loader path lux-pq
resolved, so the test binaries load without LD_LIBRARY_PATH.
Two signatures, and they are not the same thing
- A transaction stamp is one validator's ML-DSA-65 signature over one
transaction, bound to the moment it was made. It rides on the wire beside the
transaction it covers.
src/quantum.rs. - A round witness is a quorum of the committee signing one block, whose
signatures aggregate into a single certificate. It is deliberately NOT a field
of a block: the block id is the hash of the block's own bytes, so a signature
inside it would give two honest nodes two ids for one block.
src/quasar.rs.
Q-Chain sells no blockspace. Qvm::issue_tx refuses every user transaction
whatever it offers (LP-0130 §6); the chain advances only through
consensus-internal aggregation, which reaches the pool through Qvm::admit.
What proves it is the same chain
tests/vectors/q_reference.json — five vectors whose every byte came out of the
GO chain: the wires are what chains/quantumvm's encoder writes and the
verdicts are what its parser answers, ids included. tests/differential.rs
holds this crate to them and fails on any divergence.
src/wire.rs carries the Go encoder's own bytes for a transaction preimage and
for the envelope around an unsigned one, as constants, asserted byte-for-byte.
src/bin/conformance.rs is the Q evaluator for the cross-language harness
(conformance/corpus/vectors.tsv → R rows), and tests/corpus.rs runs it
from cargo test and holds it to Go's recorded answers on all 81 Q vectors.
Go, Rust and C++ agree on every compared field, block ids included.
exec is the one field this evaluator declines: acceptance is decided against a
parent and a clock, and it stands up no chain to hold either. It prints
SKIPPED, the runner excludes it, and Go and C++ still answer it — so the field
is compared, just not by this voice.
The chain identity is corpus contract. A Q block names its chain and its
network on the wire and the chain refuses one whose pair is not the one the node
serves, so the two numbers decide the verdict of every well-formed vector. They
are declared once in the evaluator and feed both the Q_CHAIN_IDENTITY row and
the binding check, so an evaluator pointed at the wrong chain says so in one row
rather than answering "belongs to another chain" eighty times.
Layout
zap.rs the serialization: a fixed section of known offsets plus a tail
wire.rs the block and transaction encodings, and canonical-or-nothing parse
ids.rs Id — lux_consensus::finality::Id, the host's own
quantum.rs ML-DSA-65: keys, the stamp, what the signature covers, batches
tx.rs a transaction, the pool, and the triage that fills a block
block.rs a block, and the rules decidable from it and its parent
quasar.rs the committee, the threshold, and the aggregate that finalizes
store.rs what the chain keeps, on disk, one all-or-nothing commit at a time
vm.rs the chain behind the host seam, and the JSON-RPC it answers
config.rs the parameters, and what an unset one settles on
error.rs every refusal this chain can make, and its shape at the seam
Decisions worth knowing
The id type is the host's. Id is lux_consensus::finality::Id, named from
the crate the node names it from. A chain that minted its own 32-byte name would
need translating at every call, and a node holding two such chains could not put
them in one map.
The seam is imported, not restated. pub use lux_node::vm as host. A
restated seam compiles against a shape the node never sees, and keeps compiling
on the day the node changes a method.
Verify/accept/reject are on the VM, keyed by id — not on the block. That is what the wire already does: those three travel as messages carrying an id, because a block handle cannot cross a process boundary.
One commit, not a sequence of writes. Store::commit takes the whole batch:
block, height index and tip pointer move together or not at all. store::Log
extends that across a crash — each batch is length-prefixed and digested, and a
torn tail is dropped whole at replay.
A missing key and a store that could not answer are different facts. Collapsing them is how one transient read failure commits genesis over a live chain: the tip reader answers the empty id for exactly one reason, and an error for every other.
Genesis is a constant of the chain, not of the moment: height 0, time 0, empty parent, this chain, this network. Wall-clock time there would give every node a different id for the same block.
Where this runtime is narrower than Go, and why
- ML-DSA-44 and ML-DSA-87 cannot be signed under here.
libluxcryptoexposes ML-DSA-65 only. Asking for either is an error that says so — never a silent downgrade to 65, which is the whole point of refusing an unknown parameter set. ML-DSA-65 is what Q-Chain validators hold. - There is no GPU batch path. The accelerator's ML-DSA batch kernel is
reachable from the Go runtime only, so batch verification here is CPU-parallel
across threads.
gpu_batch_thresholdstays in the config because it is part of the chain's configuration surface; this runtime reports it without reading it, and says so. - The committee's registration path mints keys locally, as the reference's
does, and it is the only one.
Quasar::add_validatortakes a name and a weight and makes the key pair, mirroringAddValidatorinchains/quantumvm/quasar.go. There is deliberately no door that takes a key off the wire: aggregate BLS is sound only over keys the verifier chose, so a registrant free to name its own public key namespk_a − Σpkᵢand signs a whole quorum by itself, and the same key under two names turns one honest signature into two of the threshold's slots. Minting makes both unrepresentable. Admitting a published key would need a proof of possession bound to the name registering it — and would put members in this committee that the reference cannot hold, which is a disagreement about which certificates verify.