36 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 731dc22f23 cert: a floor is read once, where it is authoritative
Red found no way past the predicate — every refusal was fail-closed. What it
found instead was the opposite failure, three times over: a number about a quorum
read somewhere that is not the authority for it.

THE DOORS IN FRONT OF THE GATE. HandleIncomingCert and VerifyCatchupCertificate
each carried a cheap sub-quorum filter over the sample committee — NovaQuorum(K)
for Nova, Alpha() for Quasar — in front of verifyCert. That is a THIRD spelling of
a quantity with one definition, and it is a different quantity: the gate derives
the accept rung's floor from the SIGNING SET, where NovaSignerFloor saturates at
three, while the door read the committee's majority, which grows with K. At six
seats the gate accepts a certificate declaring three and the door demanded four;
at nine it demanded five; at eleven, six. And where the set is SMALLER than the
committee the door asked the set for more than the set's own floor: three signers
derive two, and a door reading a committee of four asked for three.

So the exact bytes a node's own assembler produces never reached the predicate
that would have admitted them. A door in front of a gate may only refuse what the
gate refuses, and these could not, so they are gone. Nothing is lost by not asking
twice: the gate demands EQUALITY with the derived floor, which is strictly
stronger than the "at least" a door applied. Nor is any work saved by refusing
early — the certificates a floor filter drops are the ones carrying FEW votes,
which are the cheap ones, and Verify stops at the first signature that fails.

THE CALLER'S ALPHA. BuildVerifiedQuorumCert took the floor as an ARGUMENT, and on
the count-only road nothing re-derived it: one vote and an alpha of one minted an
authority token on a chain of five, while a certificate carrying those exact bytes
was refused on arrival. The docstring said an alpha that disagrees with the set
yields no token, and that was true only of the weighted road. So there is no
argument. The floor is SignerFloor(tier, SignerCount(epoch)), read at the door,
and there is nothing left for a caller to disagree with.

With that, a nil stake source is refused at BOTH rungs rather than at the export
rung alone: a floor is a property of the set, and with no set there is no floor and
nothing to mint. exportNeedsStake goes with it — one rule where there were two, and
the rule Rust and C++ already have by their types, each taking a set by reference
so the absent case is unrepresentable rather than checked. The equal-stake road
keeps minting through the engine, whose committee is its own, and keeps ADMITTING
on the arrival road, where signatures have already arrived to be weighed.

THE WIDTH. SignerFloor and Quorum returned uint32 while counting a set held in an
int, so the floor wrapped at about six billion signers and SignerFloor(Quasar, ~6.4e9)
answered 1 — a floor a lone signer meets. They return int now and are narrowed only
where a certificate is written, which is one place; and because the derived clause
compares at full width, a mint that narrowed is caught by it rather than believed.

THE LAST SECOND SPELLING. attestation.go's export trigger said config.TwoThirdsCount(n)
where it means SignerFloor(Quasar, n). Same number, and that is the point: a trigger
spelled apart from the rule it must never outrun is a trigger that can drift from it.

TestTheDoorAdmitsWhatTheGateAdmits walks the four sizes where the two numbers part,
on both arrival roads, and fails on the old filter at every one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-02 02:56:02 -07:00
zeekayandHanzo Dev 287b89c64f cert: derive the floor on the road a certificate arrives by, too
The rule landed at the door that MINTS a token from raw votes. Auditing the three
sites that promote a certificate to that token found the same hole one function
away, on the road a certificate ARRIVES by: with no stake source, verifyCert fell
to the count-only Verify, whose last clause counts distinct valid accepts against
the number the CERTIFICATE declares. So a gossiped Quasar certificate declaring a
quorum of one, carrying one signature, was promoted and finalized.

The two doors answer differently, and the difference is the threat rather than an
oversight. Minting takes the floor from the CALLER's alpha, so with no set there
is nothing to check the caller against and the export rung is refused outright.
Admitting one that arrived checks N real signatures from N distinct in-set
validators; what was missing was only that its declared quorum was its own. So the
floor is derived there too, from the node's live committee — which is that road's
authoritative view of its set. Refusing the rung outright instead would retire the
equal-stake deployment, which is a larger decision than this clause is.

THE PRICE, stated because it is real: with no stake model the committee is a
CONFIGURED number, so that road now needs the fleet to agree on it. Before, any
declaration was admitted and a node with a different K interoperated by accident.
That is the cost of not letting a certificate name its own quorum, and it is why
the minting door refuses the rung rather than weighing it — a configured committee
is strong enough to CHECK a declaration and not strong enough to ORIGINATE one.

And the count-only road reads a DIFFERENT derived number, which is the trap this
change nearly walked into. SignerFloor is the distinct-signer guard the weighted
road carries beside a stake clause; Quorum is the count that IS the predicate
where no stake clause stands beside it. They agree at Quasar and diverge at Nova
past five signers, because NovaSignerFloor saturates at three — correct as a guard
on a road whose majority is carried in stake, wrong as a majority on a road that
has only the count. assembleCertLocked already declares NovaQuorum on that arm, so
reading SignerFloor there would make a node refuse its OWN certificates at six
signers and up. Every committee the fixtures use is four or five, exactly where
the two agree, so nothing in the suite would have said so:
TestTheTwoRoadsDeriveTheNumberTheyDeclare walks 1..64 instead.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-02 02:23:17 -07:00
zeekayandHanzo Dev 8d94ad1e03 cert: derive a certificate's quorum from the set, never from the certificate
A certificate carries a Threshold, and until now that field was its own
authority on the count-only road: Verify's last clause counts distinct valid
accepts against the number the CERTIFICATE names, so one declaring 1 clears it
on a single signature. C++ has always demanded the derived floor exactly, so the
same certificate was accepted by two implementations and refused by the third.

VerifyWeighted now requires Threshold == SignerFloor(tier, n) — the floor this
set gives this rung. Equality and not a lower bound: an over-claim names a bar
this set does not set, and tolerating it would let a certificate redefine the
rung upward exactly as tolerating an under-claim lets it redefine the rung down.

SignerFloor is the one definition of a rung's count floor, read by the rung
predicates, by the assemblers and by the new clause, so the number enforced and
the number declared cannot drift.

Two corollaries the rule forces:

  No set, no export. BuildVerifiedQuorumCert refuses any tier that authorizes
  export when no stake source is supplied. That road counts votes against the
  caller's own alpha, so an export token could be minted over one signer with
  alpha of one — and AcceptWithCert trusts the token, which is the point of the
  token. The accept rung keeps the road: it authorizes local execution the chain
  can still reorg away.

  The assembler reads the same n the verifier does. assembleCertLocked derived
  Nova's declared floor from effectiveCommittee, which sizes the SAMPLE and is
  floored at the minimum Byzantine committee. At three signers the sampler said
  three and the set said two, so a node would have built a certificate its own
  peers refuse. The export path already read SignerCount.

The corpus grows five rows — three that declare a quorum of their own, and two
lopsided sets where the stake half binds alone, which an equal set cannot state
because both halves bind at one edge there. Two frozen verdicts change the clause
they are NAMED by, from the stake floor to the count, and neither changes from
refused: over an equal set a certificate short of its stake is also short of the
quorum it now has to declare.

The rungs' own count clauses are unreachable as a result — Verify proves count
reaches the declaration and the new clause proves the declaration is the floor —
and are kept as the place the floor is defined and as the guard that survives any
change to the declaration rule. Written down in LLM.md beside the other walls
behind walls.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-02 02:23:07 -07:00
zeekayandHanzo Dev 43c74b012d go: name every refusal, then check that the named one is the one given
QuorumCert.Verify returns a different typed error for each clause of the finality
predicate, precisely so one refusal can be told from another. Fourteen are
declared. Four were ever named by a test — the two the R5 floors turn on, plus
ordering and a bad signature — and the other ten had nothing asserted on them at
all: nil cert, nil verifier, version, role, unknown tier, zero threshold, no
votes, a non-accept vote, the nova stake floor. Every one of those clauses would
have passed with its body replaced by `return errors.New("no")`, which is the
shape of test that measures nothing.

So every clause now has a row: a certificate proven valid by the control above
it, one field changed, and the error that clause owes. Wrong version, wrong role,
a tier that is neither rung, a threshold of zero, no votes, a duplicate voter,
voters out of order, a reject inside a finality cert, a signature over another
position, a voter nothing can resolve, a quorum larger than the votes carried —
and on the weighted side, an unresolved set, a set below the minimum Byzantine
committee, signable stake of zero, the seats without the stake and the stake
without the seats. The same table exists in Rust and in C++; the three are meant
to be read side by side, because three implementations refusing one certificate
for three reasons is a fork waiting for the cert they disagree about.

The fifteenth name, ErrQCVotePosition, is returned by nothing. The var block's
comment says each maps 1:1 onto a clause; that one maps onto no clause, because a
vote that signed another position fails as a bad signature — the message is
rebuilt from the CERT's position and never from the vote's. It is left alone here
and reported rather than covered, since no input reaches it.

The proof-of-possession rejects follow the frozen corpus and go past it: the
class is asserted, not merely that something failed. Encoding before possession
is what keeps a pairing off a point that was never a point, and a test that only
checked for non-nil would pass with the order reversed. The corpus has no
49-byte-key case; this side carries one, because the clause is `!=` and a
verifier guarding only the short side would check a proof against a PREFIX of the
published key.

Elsewhere: the parameter doors (every clause, and zero still meaning unset),
the corpus's own stake seam (three projections over the signers, the fourth over
the roll), the two arithmetic doors that must not wrap, the warp filter/sum/
aggregate read as the step a forged message would attack, the vote frame's
refusals including a four-gigabyte signature that is not there, and the durable
guard: what survives a restart, what a floor that could not be written must not
report, and every shape of file that is present and does not decode.

  engine/chain 87.2 → 89.7 · validator 84.7 → 98.3 · pop 85.3 → 91.2
  config 99.0 → 100 · conformance 93.8 → 97.4

Of the named core, cert/finality/quorum/quasar/descent/guard/verified_cert are
whole; what remains there is four statements no caller can reach, written down in
LLM.md so the next reader does not spend an afternoon each on them. The rest of
the gap is engine.go and the VM/network integration surface, which wants
multi-node harnesses rather than unit tests.

Tests only — no consensus logic moved.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 22:19:35 -07:00
zeekayandHanzo Dev ac10e53f84 cert: give the export rung a floor on the SET, so a shrinking denominator cannot shrink past a committee
Reading both floors over the signers let a set shrink to its signers, and a rung
whose floors are read over n is satisfiable at every n. TwoThirdsCount(1) is 1, so
one signature over a one-signer set clears the count floor and the stake floor
together and mints export finality. Byzantine tolerance is f = ⌊(n−1)/3⌋, which is
ZERO for n of one, two and three: such a certificate absorbs no fault at all, and
one compromised key among its signers forges it outright.

So the export rung refuses a signing set below minBFTCommittee — the same constant
bftCommittee floors committee SELECTION at, read at the certificate instead of at
the sampler, and reused rather than restated. It is a floor on the SET, stated
beside the n<1 refusal because neither quorum floor implies it: both are read over
n and both shrink with it. Nova has no such clause and must not grow one — it
authorizes local execution the chain can still reorg away, so a small or
partitioned chain has to be able to make progress. A three-validator chain now
PRODUCES and never CERTIFIES; operators need n ≥ 4 to export with any fault
tolerance. Rust mirrors it as CertError::MinCommittee, which the corpus vocabulary
folds into Go's one belowThreshold class.

The transcription guard was reading the wrong denominator. verdict_test computed
n as len(c.Set), the membership ROLL, so a floor moved onto the roll would agree
with the guard and be re-blessed by -update — the second line of defence agreeing
with the regression it exists to catch. It now counts the seats that hold a key,
on the Quasar branch and the Nova one, and transcribes the committee floor as a
search for the least n whose fault budget reaches one rather than as the constant.
Proven by construction: with the corpus source reporting the roll as its signer
count and the golden re-blessed, the old guard passes and the new one fails.

The corpus could not have caught it either. quasar_keyless_third had three signers
on a roll of four, and ⌊2·3/3⌋+1 and ⌊2·4/3⌋+1 are both three, so its count half
was vacuous. Two rows now separate the denominators one at a time:
quasar_keyless_stake (six signers, a seventh member holding a third of the roll —
the count floor is five either way, so only stake decides) and
quasar_keyless_count (four signers, two members holding one unit each — the stake
clears under either reading, so only the count decides). TestKeylessRowsBind-
BothDenominators fails if either is lost or flattened.

quasar_keyless_third itself FLIPS to a refusal, and it is the row that states the
new clause: the recorded stake floor is 200 over the three hundred that can sign
and not 333 over the five hundred the chain carries, so the denominator IS the
signers — and the certificate is refused anyway, on neither quorum floor. Reading
the floors over the signers stops a spectator stranding a chain; it is not a way
for a chain with three signers to export. Every other frozen verdict is
byte-identical, and the admission section and the signed vectors are untouched.

StakeSource gains CarriedStake, and no floor may read it. SignerStake alone
decides a certificate correctly and cannot show the set shrinking underneath one:
a chain whose signable stake has fallen to a sliver of what it carries still
certifies at ⅔ of the sliver, and ResponsiveStakePct — voted over signer — stays
at one the whole way down. FinalityStatus.SignerStakePct is the number that falls,
and the one to alarm on. Rust has carried(), C++ has total_stake(); Go had lost
the projection when the denominator moved.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 20:52:28 -07:00
zeekayandHanzo Dev f4913a3fd7 cert: read both floors over the signers, so stake that cannot vote cannot strand export
A validator set may carry a member with no signing key. It holds stake and no
verifier will ever accept a signature from it — it is a spectator, not a party to
the vote. Both quorum floors were nonetheless read over the whole membership:
StakeSource.TotalStake summed its weight and ValidatorCount counted its seat.

That is a category error with an arithmetic consequence. Once keyless stake
reaches a third of what the chain carries, floor(2/3 * carried) is above the
whole of the signable stake, so the export rung cannot be reached even with every
signer in the set agreeing — permanently, with no misbehaviour anywhere. Past a
half, Nova goes the same way. The count floor strands the same way in seats: two
thirds of the roll can exceed the number of signatures the set is able to produce.

So the seam says what it means. TotalStake becomes SignerStake and ValidatorCount
becomes SignerCount, both read over the validators that can actually sign, and
Weight answers 0 for a keyless member as it already did for a stranger. The
rename is deliberate rather than a doc change: an implementer of the interface
has to confront the question, instead of silently keeping a denominator that
strands its own chain.

This is the two-thirds rule read over the right set, not a weakening of it.
Quorum intersection is untouched — two disjoint quorums each past 2/3 of the
signer stake would sum past the whole of it — and the fault budget simply stops
being spent on parties known in advance to cast no vote. A key absent at the
epoch is a known non-participant; BFT's third is a budget for the unknown ones.

Rust follows: ValidatorSet is both the verifier and the source, and its
insert_unkeyed door made this reachable through a public API. It now keeps
`signable` beside `total` — total still bounds admission, because a set whose
weights cannot be represented is refused whether or not the seat that overflows
it can sign, and signable is what a quorum is measured against. carried() names
the first for what it is.

C++ needed no change and is the reason to believe the shape: a Validator there IS
a public key and the engine's set is keyed by one, so a spectator has never had a
slot to occupy and total_stake_ was always the signer stake.

Corpus: quasar_keyless_third, the case the R5 TODO could not state. Three seats
of a hundred hold keys, a fourth holds two hundred and none; all three signers
sign and the export rung admits it on 300 of 300, where the roll denominator
would refuse it against floor(2*500/3)=333 forever. Every existing verdict is
byte-identical — the seat gains an omitempty keyless flag, so a set of ordinary
seats encodes exactly as before.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 19:07:15 -07:00
zeekayandHanzo Dev 9f2d755bb0 cert: the door is sized from K, the verifier from n — say which
TestQuasarCountFloor_SizerMatchesVerifier and LLM.md both claimed the
producer, the gossip door and the verifier ask for one count. What the
test holds is narrower and is the thing worth holding: the door's alpha
is TwoThirdsCount(p.K), so the door is sized by the function the verifier
enforces and neither is a second definition of the count.

The two are one number whenever the params were sized to the set that is
running, which is what FeasibleParams does. They part when the set has
moved since, and the new test states which way that cuts: TwoThirdsCount
does not decrease, so a set that GREW leaves the door the looser bar and
the predicate still decides — this clause cannot halt an export the door
admitted. A set that SHRANK leaves the door the stricter bar, which is
the runtime's gossip-admission bound and reads the same before and after
the clause.

Documentation and one test. No floor logic is touched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 18:22:29 -07:00
zeekayandHanzo Dev 82ab92a70f cert: give the export rung a distinct-signer floor, so two thirds means two thirds of the parties
The Quasar rung read only stake. Two thirds of the stake is one signature
wherever two thirds of the stake is one validator, so a whale holding a
supermajority could mint an export-grade certificate alone — and export-grade
finality is what a bridge, a settlement and a cross-chain message admit on. A
certificate one key can produce is not a claim that a Byzantine supermajority
agreed, however much weight stands behind the key.

VerifyWeighted now demands config.TwoThirdsCount(n) = floor(2n/3)+1 DISTINCT
signers of a Quasar certificate as well as the stake. Two independent
predicates, neither sufficient alone, the same shape Nova has one rung up.

TwoThirdsCount is the count that value has always been, given its own name and
one home. It is derived from TwoThirdsStakeFloor over unit weights rather than
restated, so the count and the stake predicate cannot drift. It replaces three
spellings of the same closed form that had grown apart in reach if not yet in
value: config.EqualStakeSupermajorityThreshold (the alpha sizer), the private
chain.quasarQuorum (the attestor's assembly trigger), and the clause that was
missing from the verifier entirely.

Stake is read first and the count second. Both must hold, so the order names
only which clause a refusal reports: on an equal-stake set the two are one bar
in two units and bind together, and the count clause binds ALONE only where the
weights are lopsided — so reaching it means the stake was there and the signers
were not. Every verdict the R3 corpus froze is unchanged.

Liveness. Nothing that exports today stops exporting. QuasarAttestor, the only
producer of export certificates, already assembled at exactly floor(2n/3)+1
attestations, and FeasibleParams already sized alpha — the floor
HandleIncomingCert applies to a gossiped Quasar cert — to the same number.
Producer, gossip door and verifier now read one function, so the verifier's new
clause refuses nothing the engine can build and exactly what it never could.
quasar_count_floor_test.go holds all three to that.

The tests that moved were the ones written on three seats, where floor(2n/3)+1
is three and no two-signer certificate can isolate the stake half any more.
They ask the same question on four.

Rust: the same clause in Cert::verify_quasar_supermajority, the same name.
1.22.4 -> 1.22.5 — this changes what the crate accepts.

Also, at the already-admitted door: the zero-weight refusal is justified by
agreement with Register, not by the phantom-signer argument alone. A seat whose
key does not decode can never sign and is refused anyway, because the weight is
read before the key is — and deferring it would make admission depend on which
keys a node's crypto build can parse.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 17:39:39 -07:00
zeekay b660d19f51 the face is Zen, and it ships from @hanzo/font
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.

Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.

Comments naming a face we no longer use are deleted, not reworded.
2026-09-01 08:12:22 -07:00
zeekayandHanzo Dev b41c532fa4 rust: derive θ with SHA-256, and verify the signatures
Two implementations of one protocol disagreed about when a round accepts
and about what a certificate proves, and nothing in the repository said
so because each agreed with itself.

θ. The selector's doc comment said "PRF (SHA256)" and the code called a
SipHash-like mixer. Go's protocol/wave/fpc computes θ with crypto/sha256,
and α = ⌈θ·k⌉ is the vote count a round accepts on, so the two nodes
applied different accept counts to identical votes: at phase 0 with k=20
the mixer returns 11 where Go returns 15. Measured both ways — the mixer
is spliced back in under tests/conformance.rs and the check fails on θ
before it reaches α. The seed was worse than the PRF: three hardcoded
ASCII constants, public forever, so every future threshold was readable
in advance. derive_epoch_seed is Go's DeriveEpochSeed, which binds the
previous epoch's block hash and is therefore unknown while the current
epoch is still open.

The phase now advances once per counted round, past the k-vote gate,
which is where Go advances it. It was advancing once per vote, k times
too fast, so two nodes one round apart read θ from different phases.

Signatures. verify_certificate ended in

    !cert.aggregated_sig.is_empty() || !cert.quantum_sigs.is_empty()

and the aggregator it asked about returned vec![0u8; 48] whenever it had
nothing to aggregate. Zero bytes are not empty. It had nothing to
aggregate on every input, because it sliced each signature to 48 bytes
and handed it to a G2 decoder: luxfi/crypto/bls signs under
BLS_SIG_BLS12381G2, so a public key is 48 bytes and a signature is 96,
and this crate had the two the other way round. So forty validators
voting with no signature at all produced a certificate that verified,
and there was no input for which it did not.

finality::Cert is engine/chain.QuorumCert — the position, the rung, the
threshold, and one signature per named signer. It encodes to the bytes
the network gossips, decodes strictly, and verifies Go's seven clauses:
version, role, tier, non-zero threshold, non-empty votes, strictly
increasing signers, accept-only, and every signature checked against a
registered key over the message the cert's OWN position derives. A
signature does not travel between positions. verify_stake adds the rung's
stake predicate, re-derived from the live set rather than read from the
cert, so a Nova quorum relabeled Quasar fails; an unresolved set fails
closed.

The engine no longer issues certificates. Its block carries an id, a
parent, a height and a payload, and a certificate binds a chain id, a
round and three roots — nothing it can assemble is a statement the
network signs, so what stood there manufactured finality out of a
position it did not have and votes it had not checked.

The corpus carries the rule now: 8 epoch seeds and 192 thresholds read
from the live Go selector, θ as exact IEEE-754 bits because α is a
ceiling and the last bit decides. Certificates are compared over their
whole wire form; the previous check read four header bytes.

Also removed: build.rs linked a C library no Rust symbol references, and
libc was unused. BENCHMARK_RESULTS.md reported a benchmark that has not
compiled in a long time — it imported five names this crate does not
export — so the benchmark is rewritten against the real API and the
retracted report is gone.

33 tests, no warnings.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 23:52:34 -07:00
zeekayandHanzo Dev c4fe410e6e statesync: answer a peer's summary requests, and drop an unreachable check
A node could adopt a peer's state and could not serve one. Adoption already
existed in engine/chain/summary; nothing answered the two requests it makes, so
every node was a client of a service no node provided.

The serving half reads the VM and returns what to send, leaving framing and
transport to the node. It never consults StateSyncEnabled: a node's own
preference says nothing about what its peers may ask, and a chain that answers
only when it would also ask leaves a fleet unable to repair itself the moment its
healthy members have nothing left to sync to.

Silence and an empty reply are distinct answers. No summary, or an error, sends
nothing and the requester closes its round on its deadline. A request naming no
heights gets an empty reply — "none of those" — which completes the tally at
once. Swap them and every round either stalls or under-counts.

The op-space carries no wire for a singular by-height request, so nothing here
answers one.

Also removes the per-summary overflow check in elect. Its stake only ever sums a
subset of the ballots `answered` already sums, and `answered` is checked one line
earlier per ballot, so the second check could never fire. The invariant is now
stated where the addition happens. Coverage is 100% on both halves, reached by
deleting the branch rather than by asserting an impossible case.

Comments across the engine drop incident detail for the rule each one holds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-15 21:52:51 -07:00
zeekayandHanzo Dev 21287a898c test(engine/chain): make TestRestartPreservesState assert real state
The old body checked only IsBootstrapped/HealthCheck across Stop/Start;
an engine that dropped every accepted block on Stop still passed. Now
the test drives a real signed 4-of-5 quorum accept through the Quasar
export cert BEFORE the stop, asserts the consensus-critical triple
(finalizedByCert set, Preference, QuasarHeight) survives the restart
unchanged, and then finalizes and certifies a SECOND height on the
restarted engine — a restart must preserve the past and keep finalizing
the future.

Negative control executed: a Stop that clears finalizedByCert fails at
'restart must not lose an accepted block'; the old body passed under
that same mutilation. LLM.md also records the pre-existing
TestNovaQuasarMatrix_FourOfFive_ExportsQuasar flake (fails at HEAD with
this change stashed).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 09:12:46 -07:00
zeekayandHanzo Dev 87270f9080 docs: retract fabricated throughput numbers (honesty pass)
Remove impossible/mislabeled per-second figures from consensus docs: the
"3.9B blocks/sec" / "6.6B votes/sec" u8-overflow artifacts (real C-FFI ≈21K
votes/sec per the CI README), the 20.26M/11.5M "TPS" that are ZAP wire-message
microbenches (relabeled, not consensus finality), stub-SDK "sub-second finality"
claims, and downgraded "production-ready" labels on SDKs the CI audit documents
as stubs. Genuinely-measured go-test numbers left intact. Fixed stale
LUX_CONSENSUS_PQ_MODE → CONSENSUS_PQ_MODE. Docs only, no Go logic touched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 08:53:06 -07:00
zeekay 45363966ab consensus: refresh ConsensusCert doc to v1.26.0 compact-cert evidence layer
LLM.md: four typed EvidenceKinds (beam-bls / pulsar-threshold-mldsa / corona-ringtail / p3q-mldsa-rollup) plus the Magnetar SLH-DSA 5th leg outside the vocabulary; KeyEra decomplect (value-not-place) over the Pulsar+Corona threshold lanes only (Beam/P3Q refused by eraSuite); 4-posture policy table (STRICT = Beam^Pulsar^Corona); P3Q fallback bound to the committed validator set; O(1)/957x measured (>=100x asserted); deliberately boring, NOT constant-time verifier; mpc/tee/cc decomplected. Latest Tag -> v1.26.0.
2026-06-27 14:15:02 -07:00
zooqueen d01dcfa1c1 feat(quasar): ConsensusCert envelope — policy-gated threshold certification
Canonical finality cert: one policy-gated envelope (VerifyConsensusCert) with
per-leg evidence modes — ThresholdSig (lattice O(1)), WeightedSigSet (independent
FIPS sigs + weighted-Merkle quorum; the only mode for SLH-DSA, never
seed-reconstructed), StarkCompressedSigSet (P3Q, same predicate, audit-gated),
ClassicalAggregate (BLS, never satisfies a PQ leg). Required legs are
policy-derived (CertPolicy.RequiredLegs), never cert bytes (Red H3 anti-downgrade);
full-tuple domain binding. WeightedQuorumCert foundation: policy-as-parameter,
message recomputed internally, strictly-increasing IDs, checked weight.

27 named envelope tests + F1-F5 + 6 CTO attacks PASS; verifier total (fuzz, zero
panics); a fuzz-found threshold-sig flags-byte malleability fixed. Fixed go.sum
[email protected] to canonical transparency-log hashes. Scrubbed ~54MB tracked binaries.
2026-06-21 20:39:11 -07:00
zeekay 1ae235f9ab brand-scrub: drop 'Lux vs Avalanche' framing in LLM.md
LLM.md is repo-doc, not academic prose; neutralize benchmark framing to
'Lux vs prior-gen EVM' per workspace brand policy. The actual citations
in consensus/paper/ remain intact (academic prior-art).
2026-06-02 22:59:21 -07:00
zeekay 38a51dd01b consensus: LLM.md note ValidatorSchemeID landed at v1.23.4 2026-05-10 20:26:19 -07:00
zeekay c6aa940361 consensus: ValidatorSchemeID cross-axis gate for NodeIDScheme
ChainSecurityProfile.ValidatorSchemeID() (type alias of SigSchemeID)
returns the IdentitySchemeID byte that pins which luxfi/ids.NodeIDScheme
a peer / proposer / validator / mempool sender MUST present on this chain.

ChainSecurityProfile.AcceptsValidatorScheme(presented, classicalCompatUnsafe)
is the single cross-axis check. Strict-PQ + FIPS profiles refuse the
classical (0x90) scheme regardless of the operator opt-in flag (defence
in depth); permissive profiles honour the flag. Unknown bytes outside
the named-classical secp256k1 byte are refused at every profile class.

The SigSchemeID byte assignments are shared with luxfi/ids.NodeIDScheme
by design (0x42 = ML-DSA-65, 0x43 = ML-DSA-87, 0x90 = secp256k1).
TestValidatorScheme_AlignedWithIdsEnum pins the alignment in literal
form so a renumber in either enum trips CI.

ErrValidatorSchemeMismatch is the typed error consumers match against;
mirrors luxfi/ids.ErrNodeIDSchemeMismatch at the wire-decode layer.

Tests:
- TestValidatorSchemeID_PinsIdentityScheme
- TestAcceptsValidatorScheme_Matched
- TestAcceptsValidatorScheme_StrictPQ_RejectsCrossPQScheme
- TestAcceptsValidatorScheme_StrictPQ_RejectsClassicalEvenUnderUnsafeFlag
- TestAcceptsValidatorScheme_Permissive_AcceptsClassicalUnderUnsafeFlag
- TestAcceptsValidatorScheme_RejectsUnknownByte
- TestSigSchemeID_IsClassicalCompatUnsafe
- TestValidatorScheme_AlignedWithIdsEnum

Patch-bump: v1.23.2 -> v1.23.3.
2026-05-10 20:18:08 -07:00
zeekay afe7e65e27 CLAUDE.md: M/F-Chain canonical per LP-134 (T-Chain reserved for teleportvm) 2026-05-05 19:15:03 -07:00
zeekay 5783c0cb41 consensus: PQ canonical terminology (FIPS 203/204/205 + Pulsar + Lamport) 2026-05-05 17:52:32 -07:00
zeekay dbd42fe9cd docs: drop dated header lines, document configurable PQ mode in README
- Strip 'Last Updated/Date 2026-*' header lines (per house style: dates
  belong in git log, not in middle of files)
- README: add PQ Mode Selection section with env-driven config table,
  bench results, and Pulsar pinning + 33 KB cert size honest accounting
- Add cert_roundtrip_test.go (real triple-sign -> marshal -> unmarshal
  -> verify with real keys -> reject with wrong keys)
2026-05-05 12:30:44 -07:00
zeekay a89486a9d3 LLM.md: pin Pulsar (DKG2 + Pulsar-SHA3) + honest 33KB cert size 2026-05-05 11:45:20 -07:00
zeekay a1ac5c2fdd LLM.md: drop date, add PQ mode docs + bench results 2026-05-05 10:22:20 -07:00
zeekay 04ce8745ee consensus: Quasar cutover to LSS-Pulsar + post-Feb-28 batch
Squashed batch covering all post-Feb-28 consensus work, capped by the
LSS-Pulsar cutover (task #53):

- Quasar cutover from upstream Corona to LSS-Pulsar adapter:
    go.mod: drop direct require github.com/luxfi/corona; add
            github.com/luxfi/pulsar + github.com/luxfi/threshold;
            local replaces => ../pulsar, ../threshold
    epoch.go: InitializeEpoch → keyera.Bootstrap; RotateEpoch →
              lss.DynamicResharePulsar gated by activation cert
              verification under unchanged GroupKey
    quasar.go, grouped_threshold.go: import swap corona → pulsar
    reshare_epoch.go: production wiring; QUASAR-PULSAR-ACTIVATE-v1
                      domain prefix; ReshareEpoch + RefreshEpoch
    EpochKeys: gains KeyEraID, Generation, RollbackFrom (LP-105)
    BundleSigner: re-derives Lambda for participating subset
    258 quasar tests pass.

- Earlier post-Feb-28 work (gofmt linter fixes across protocol files,
  METASTABLE_SIGNED_QUERIES.md doc, MLDSAProof rename across 7 test
  files) included in the squash.

Hash profile: consensus passes nil to resolve to Pulsar-SHA3 default
(LP-105 / pulsar/hash). Activation message canonical bytes bind
chain_id, network_id, key_era_id, generation, group_id, epoch, set
hashes, thresholds, group_pk_hash, nebula_root, hash_suite_id,
implementation_version, variant, reshare_transcript_hash —
all under TupleHash256("PULSAR-TRANSCRIPT-v1", ...).

Vocabulary alignment with LP-105 (Lux Finality Stack lexicon):
  Photon (vote) → Beam (BLS) → ML-DSA (PQ identity) →
  Pulsar (PQ threshold kernel) → Pulse (cert over Nebula root) →
  Prism (composition verifier) → Horizon (finality boundary) →
  Quasar (full consensus).

Excluded from this commit: pkg/wire/{candidate,policies_test}.go
uncommitted diff (pre-existing local edits; stashed and restored).
Untracked protocol/quasar/witness_producer.go (mtime Apr 29; pre-task).
2026-03-03 12:00:00 -08:00
zeekay 4d49cfcfe7 docs: Z-Chain ZKP of BLS ∧ ML-DSA ∧ Corona — PQProof is Groth16 SNARK
QuasarCert.PQProof carries a Z-Chain Groth16 proof (~192 bytes)
proving all three signature layers (BLS + ML-DSA + Corona) are valid.
Certificate: 48 B (BLS) + 192 B (ZKP) = ~240 bytes total.
2026-02-15 17:46:09 -08:00
zeekay 54ca07523d docs: audit and correct all consensus documentation
Verified every protocol/*.go against its doc.go. Fixed all inaccuracies:

- README.md: rewrote to match actual code; removed bogus SDK claims
  (C/Rust/C++ stubs presented as "100% test parity"), impossible
  benchmark numbers (0.33ns Quasar Phase I = sub-clock-cycle), stale
  version badge (v1.22.0 vs actual v1.22.84), wrong protocol
  descriptions (nebula as "state sync", nova as "parallel chain"),
  missing protocols (ray, field not listed)
- doc.go: stripped AI fiction (QZMQ, shared hallucinations, AI-powered
  validation, Dilithium/Kyber naming), replaced with accurate Quasar
  family description
- LLM.md: stripped 40KB of old session logs down to essential reference;
  accurate protocol table, honest SDK status, measured benchmarks only
- docs/index.mdx: fixed nebula/nova labels, removed impossible benchmark
- protocol/nebula/doc.go: fixed "consensus utils" typo
2026-02-15 17:40:11 -08:00
zeekay 9d1fa34a3a rename: Triple Consensus → Quasar in all prose and comments 2026-02-15 17:30:35 -08:00
zeekay 299fa7336f clean: remove deprecated aliases, AI slop, backwards compat code
- Delete DeprecatedConfig/NewFromDeprecated in syncer (49 lines)
- Delete ChainVMWithNetwork/ChainVMWithHealth type aliases
- Delete QBlock, newSignerWithThreshold, VMState aliases
- Delete GetNetID (returned empty string)
- Remove all "In production" / "For now" / "placeholder" comments
- Fix examples: NewSimpleModel API, big.NewInt overflow
- Published deps for all examples (consensus v1.22.84, dex v1.4.1)
2026-02-15 14:08:02 -08:00
zeekay eac34dc22a deps: luxfi/zapdb v4 → v1.0.0 (module path reset) 2026-02-01 00:05:14 -08:00
zeekay 4ff02b91a0 clean: remove Snow/Avalanche references — Lux consensus is original work 2026-01-15 13:27:21 -08:00
zeekay 07af7e314c docs: add LLM.md project guide 2026-03-11 10:15:03 -07:00
zeekay be8d6cadfe chore: remove AI-generated docs from tracking 2026-02-20 22:02:46 -08:00
zeekay 257cacb84a chore: sync dependencies and format code 2026-02-04 15:44:47 -08:00
zeekay 79eb047505 feat: unify Python SDK with MCP mesh consensus
Major changes:
- Merge pkg/wire Python types into pkg/python/lux_consensus
- Add MCP mesh consensus module (from hanzo-consensus)
- Add types.py with Candidate, Vote, Certificate for wire protocol
- Bridge functions for AI consensus -> blockchain interop
- Keep pkg/wire as Go-only wire protocol types

Python SDK now provides:
- lux_consensus: Core chain/block/vote types
- lux_consensus.types: Wire protocol (Candidate, WireVote, WireCertificate)
- lux_consensus.mcp: MCP mesh for agent-to-agent consensus

Also includes engine improvements:
- Chain syncer with persistence
- PQ crypto enhancements
- External sequencer finality example
- Protocol field/horizon/prism updates

All tests passing.
2026-01-13 09:49:03 -08:00
zeekay 4d6f5562d9 chore: sync with node requirements, add runtime package 2026-01-12 14:44:26 -08:00
zeekay e925cca669 docs: add LLM.md for AI assistant documentation 2025-12-24 18:10:34 -08:00