5 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 33e6b708c3 cargo fmt
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 09:53:52 -07:00
zeekayandHanzo Dev ab41ba6b1c rust: the eligibility rule at registration, on the identity the proof binds
`ValidatorSet` admitted a key on a proof of possession and refused a key
already held by another node, and stopped there. Three things the Go rule
turns on were missing, and this closes them by naming the same clauses in
the same order as `validator/registration.go`:

  NO KEY       a registration with no key wanted the unkeyed door
  ZERO WEIGHT  a keyed signer with no stake is a phantom signer — it raises
               the count a floor is read against without raising the weight
  ENCODING     the key is a canonical compressed BLS12-381 G1 point
  POSSESSION   a node-bound proof binds THIS key to THIS node
  UNIQUENESS   the key is registered to no node, AND the node to no key

The node axis is the new half of uniqueness, and possession cannot stand in
for it: one operator registering two keys it genuinely holds under one id
offers two genuine proofs, and takes two signer slots and two shares of the
weight. So a node is admitted exactly once, on both doors. Re-keying is a
`remove` and a fresh admission — the one spelling that cannot change a live
member's key or weight behind a caller that thought it was adding one.

And registration now calls `pop::verify` rather than restating it. There was
a second proof-of-possession implementation here, with its own copy of the
domain tag and the preimage; two homes for one standard is how the two
drift, so there is one, and it is the one the frozen Go vectors pin.

THE IDENTITY. A voter was a 32-byte block `Id`. Go names a validator by
`ids.NodeID`, 20 bytes, and the proof signs `node(20) ‖ key(48)` — so the
proof the network makes was not the proof this crate checked, and the two
were never the same standard however well each held on its own. The voter
is now `pop::NodeId`, one value with one width at all three places it is
read: registration, the proof preimage, and the strictly-ascending vote
order. `types::NodeID` is a 20-byte type of its own rather than an alias of
the block id, with no lossy constructor — truncating 32 bytes into 20 maps
distinct nodes onto one identity, and one identity is one signer slot.

No signed byte moves: the node id enters no signed message, so Go's frozen
vote message and Go's signatures verify unchanged.

Verified: 83 tests green, `pop_conformance` and `cert_conformance`
byte-identical; clippy clean. Eight mutants applied and killed, one per
clause — possession dropped, each uniqueness axis dropped, zero weight
admitted, no-key admitted, the weight clause reordered behind possession,
the preimage padded to 32 bytes, and the unkeyed door restating a member.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 11:08:02 -07:00
zeekay 511265b10c quasar: count only members, and certify only what is signed
Three holes in the probabilistic engine's finality side, all of the same
shape: something that looked like evidence was accepted as evidence.

record_vote consulted no validator set. Nine unregistered node ids could
carry a block to Accepted against an engine holding no validators at
all, because the sample was whoever sent a message rather than the
committee. Membership is now required, and an empty set counts nobody.

create_certificate counted ballots and aggregated whatever bytes it
found. Its aggregation could not have worked — it read 48 bytes of a
signature that is 96 — so it fell back to a zero aggregate, which the
structural check then accepted. It now verifies each signature under the
voter's registered key over the position's canonical message, and issues
nothing when there is nothing to issue. Unsigned ballots yield NoQuorum
rather than an empty certificate.

verify_certificate checks that aggregate against the aggregate of the
signers' keys, over the message rebuilt from the certificate's own
position — so a certificate carries the position it attests, because
without it a holder can only check the shape.

quantum_sigs was filled by cloning the BLS signature under a second
name, so a certificate appeared to carry post-quantum evidence it did
not have. It is empty until there is a post-quantum signer.

The validator set is now one structure, cert::ValidatorSet, holding
membership, stake and keys together. A member with no key still counts
toward the set size and total stake — which raises the bar for everyone
else — and can never contribute a verified signature.

test_consensus_flow voted with zero validators registered and asserted
success, so it encoded the first hole. It now registers the committee it
votes as, and a new test pins the refusal.
2026-08-28 23:42:05 -07:00
zeekay 7a1591ee63 feat: update consensus v1.22.1 - context improvements and TypeScript SDK
- Add context extension methods for uptime and node info
- Add NoOpCalculator for uptime in test contexts
- Update locked calculator with proper interface compliance
- Remove experimental quasar event horizon duplicates
- Add TypeScript/NAPI SDK bindings for consensus
- Update Rust bindings and tests
2025-12-03 23:56:12 -08:00
zeekay 4bbeffe36e fix: update all tests and commands to use new consensus API
- Fixed cmd/checker and cmd/consensus to use new Engine interface
- Fixed integration tests to use GetConfig instead of Config()
- Updated Python SDK tests for new Chain API
- Added basic Rust tests that pass
- Fixed import paths from luxfi/node to local packages
- Moved test files to proper directories
2025-11-13 00:22:56 -08:00