Chasing these one at a time was whack-a-mole — every repair surfaced the next
moved tag (age, pq, threshold, zap, consensus, constants, sdk, keys…). This is
the systematic pass instead: every distinct luxfi/hanzoai module@version in the
workspace (692 of them) was resolved against proxy.golang.org + a direct fetch,
and any go.sum line disagreeing with what is actually served was corrected.
Not an attack, and checked rather than assumed: for each case the proxy and a
direct fetch AGREE with each other and differ from the recorded hash. Two
independent transports agreeing means nothing is rewriting bytes in flight —
the tags moved at source. sum.golang.org still holds the original, but
GOPRIVATE covers github.com/luxfi/* with GOSUMDB=off, so our own modules never
consult the checksum DB and a moved tag splits consumers silently instead of
failing at publish time.
Only the recorded hash changes. No selected version moves, and `go mod tidy`
was deliberately NOT run, so nothing is upgraded as a side effect.
Verified: `go list -m all` resolves with no checksum error.
The durable fix is upstream: treat a published version as immutable. Cut
x.y.z+1 instead of moving a tag — with GOSUMDB off, no consumer can detect it.
Several luxfi versions carry TWO different contents across this workspace,
because published tags were moved instead of a new patch being cut:
age@v1.5.0, pq@v1.0.3, threshold@v1.9.4, zap@v0.6.0, zap@v0.8.1.
Adjudicated before editing, since "checksum mismatch / SECURITY ERROR" is also
what a real supply-chain attack looks like. It is not one here: sum.golang.org
holds the OLD hash while proxy.golang.org and a direct fetch BOTH serve the same
NEW bytes. Two independent transports agreeing means nothing is rewriting
content in flight — the tag moved at source. The sumdb entry is a fossil:
GOPRIVATE covers github.com/luxfi/* with GOSUMDB=off, so our own modules never
consult the checksum DB and a moved tag splits consumers silently.
The old bytes are served by nothing now, so a stale pin can never build.
Corrected to the only content that exists, in BOTH line forms (h1: and
/go.mod h1:) — Go reports these one at a time, so a partial fix just relocates
the error.
Deliberately no `go mod tidy`: this changes no selected version, only the
recorded hash of versions already chosen.
Verified: `go list -m all` resolves with no checksum error.
The durable fix is upstream: never move a published tag, cut x.y.z+1 instead.
crypto/aggregated ThresholdSignature.Verify ignored its message argument and
returned true for any non-empty []byte — a method named Verify that verifies
nothing. Zero importers workspace-wide, so it was never reachable; removing it
before it ever could be. Real threshold verification lives in luxfi/threshold.
Verified: CGO_ENABLED=0 go build ./... clean, no remaining references.
- delete crypto/aggregated: ThresholdSignature.Verify ignored the message and
returned true for any non-empty []byte. Zero importers workspace-wide, so it
was never reachable — a loaded gun, not a live wound. Real threshold
verification lives in github.com/luxfi/threshold.
- untrack 5 committed Go test binaries (24.2MB) and add *.test to .gitignore
so they stop regenerating into the index.
Verified: CGO_ENABLED=0 go build ./... clean; no remaining references.
The canonical ML-DSA package (mldsa/) is the one way; mldsa/.old was
unreferenced legacy code. Build stays green (go build ./mldsa/ ok).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The native BLS Scheme impl (threshold/bls/) moved to
luxfi/threshold/scheme/bls so all threshold-signature implementations
live in one module. crypto/threshold now holds only the shared contract
(Scheme/Signer/Aggregator/Verifier/registry/session/errors/adapter) that
crypto/signer + hsm depend on — the one low-level seam that must stay
here to avoid a crypto<->threshold module cycle.
signer_test repoints its BLS registration to the new path. No
production crypto code changes.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Replaces the stub trusted-dealer keygen with a real dealerless DKG:
each party samples its own degree-(t-1) polynomial + Feldman
commitments; party j's share = Σ_i f_i(j+1); group key = Σ_i C_{i,0}.
No party ever forms the group secret — it exists only as the sum of
independent contributions. Every cross-party VSS share is
Feldman-verified (A·f(j) == Σ jᵏ·C_k); a bad share fails the ceremony
identifying the emitter.
Tested (CGO, BLS12-381 via circl): 3-of-5 shares sign, aggregate via
Lagrange, and verify under the emergent group key; two disjoint signer
subsets both verify (proving one shared polynomial); bad params
rejected.
NOTE: per the threshold-consolidation directive this scheme's canonical
home is moving to luxfi/threshold, and dealerless keygen is being
routed through the purpose-built luxfi/dkg engine — see the
threshold-architecture consolidation plan. This lands the tested
no-dealer property now; the engine swap follows.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Corona is Module-LWE (Ringtail/Raccoon line, ePrint 2024/1113), the
same family as Pulsar (ML-DSA) and ML-KEM. The stack comment already
labels Pulsar (MLWE); Corona was the lone RLWE mislabel. Comment only.
Real parsers for the three production TEE quote formats, decoding the documented binary layouts and
verifying the full vendor trust chain (no longer the canonical-model stand-in):
- SGX (DCAP v3): header+report body offsets (MRENCLAVE@112, report_data@368); PCK chain→pinned Intel
root, PCK signs the QE report, the QE report binds the attestation key (sha256(attestPub‖authData)),
the attestation key signs the quote — full DCAP flow.
- TDX (DCAP v4): version 4 / tee_type 0x81, MRTD@184, report_data@568; same sigData chain.
- SEV-SNP: 1184-byte report, measurement@0x90, report_data@0x50, P-384 VCEK signature@0x2A0 (LE
scalars), VCEK→pinned AMD root.
All verify the operator-key binding in report_data. 9/9 incl. tampered-measurement (sig fails),
unpinned-root (chain fails), unbound-key for each format.
- promptseal: KEM X25519 → X-Wing (X25519 + ML-KEM-768), making the prompt-confidentiality envelope
POST-QUANTUM (secure if either half holds) — matching the strict-PQ posture of ML-DSA staking. The
proof layer was already PQ (keccak + information-theoretic Freivalds). 6/6 still green.
- attestation: a REAL TEE quote verifier (closes the audit's TEE stub). Verify() checks an ECDSA-P256
report signature, an x509 cert chain to a PINNED vendor root, a measurement allow-list, AND that the
report binds the operator's confidentiality key — so a sealed prompt opens ONLY inside the attested
enclave (custody end to end). 6/6 incl. forged-sig, unpinned-root (an attacker's own well-formed
quote is rejected), wrong-measurement, unbound-key, tampered-measurement.
- poi/scale_test.go: 1/10/100/1000-node scaling. VerifyOpening is per-node-independent → linear
aggregate throughput (542 → 542k proofs/sec) at constant latency, and fraud-caught probability
→ 1.0 as nodes grow. + BenchmarkVerifyOpening.
Seals a prompt to an operator's registered KEM key so it is NEVER plaintext on the wire and only
the chosen operator opens it (RFC 9180 base mode, X25519-HKDF-SHA256 + ChaCha20-Poly1305, over
cloudflare/circl). aad binds the request (intentID) — no cross-request replay. 6/6: round-trip,
wrong-key-can't-open, tampered-ciphertext-rejected, wrong-aad-rejected, observer-learns-nothing
(no plaintext in blob + two seals differ), malformed-key-rejected.
Mirrors the Rust red-team against the canonical Go verifier the chain consumes: the pre-fit-challenge
killer (forge passes a guessed r1, fails a beacon-fresh r2 — proves the post-commit beacon is
load-bearing), a griefer's uncommitted opening can't slash, the wire decoder never panics on fuzzed
frames, and over-large dims are rejected. go test ./poi: ok.
EncodeOpening/DecodeOpening: one canonical frame (root|index|beacon|A|B|C|proof) shared by the Go
prover, the aivmbridge precompile, and the Solidity reference, so a real-model-sized opening is
verified NATIVELY on-chain instead of in gas-bounded EVM bytecode. CheckOpening/CheckDecoded return
(included, freivaldsOK) — the two bits the gate needs (genuine = both; fraud = included && !ok).
Bounds every length (MaxOpeningDim) so a malformed frame can't over-allocate. Tests: round-trip,
honest, fraud, not-included (griefer), truncated. go test ./poi: ok.
Mirror of hanzo-engine/src/poi_transcript.rs: ProofTranscript (keccak Merkle over per-matmul
leaves), ExactMatmul (whole-K i64), VerifyOpening (Merkle inclusion + Freivalds), ChallengeIndex.
VerifyOpening is the production caller the audit found missing — the off-chain watcher imports it
and slashes a bond when an opening fails. Tests: transcript round-trip, fabricated-output-caught,
swapped-reveal-caught, and a pinned golden DeriveChallenges vector mirrored byte-for-byte in the
Rust suite (cross-language parity enforced, not assumed). go test ./poi: ok.
The Proof-of-Inference verifier primitive in its proper home, beside
keccak.go (which the aivmbridge already imports). Freivalds matmul check
over F_p (p=2^61-1) on the exact int8 i32 accumulator: an honest A*B
passes; a single tampered output entry (incl. a buried off-by-one in a
16x24 matmul) is caught; signed entries reduced correctly; keccak-derived
challenges deterministic; wrong dims fail closed. All tests pass.
This is the canonical verifier the chain consumes (chains/aivm settlement,
the computeattest precompile). hanzo-engine/src/poi.rs is the Rust
prover-side mirror; luxcpp/crypto is the C++ native mirror. The forward-
pass commitment + transcript binding are not here (engine/chain side); no
end-to-end proof is emitted yet.
Three additive, fail-closed hardenings on the BLS boundary, with the security-
critical regressions on the purego (CIRCL, //go:build !cgo) path — the canonical
CGO_ENABLED=0 node image:
HIGH-1 (panic-DoS). CIRCL's bls12381 G1/G2 SetBytes accepts the MALFORMED
encoding 'infinity bit set, compression bit clear' (top byte b[0]&0xC0 == 0x40):
it computes the UNCOMPRESSED length and slices b[1:96]/b[1:192] on a canonical
48/96-byte COMPRESSED buffer -> slice-bounds-out-of-range PANIC. On a purego node a
single 0x40||zeros blob in a proof-of-possession / peer-handshake / warp / quasar
BLS field is an unauthenticated, consensus-halting DoS. Reject b[0]&0xC0 == 0x40 at
the compressed length BEFORE SetBytes, in-band (not recover()), restoring parity
with blst's erroring Uncompress. Guards: PublicKeyFromCompressedBytes,
SignatureFromBytes. Regression: sig_infinity_unset_compression_test.go.
RESIDUAL-C (identity at one boundary). Move identity rejection to the SINGLE
deserialization boundary: PublicKeyFromCompressedBytes / FromValidUncompressedBytes
call Validate()/KeyValidate() (= !IsIdentity() && on-curve && in-subgroup), so an
identity (or off-curve) key never escapes a constructor. Verify/VerifyProofOfPossession
drop their redundant per-call identity test — which was WRONG anyway (it compared
against 0x00||zeros; the canonical compressed-G1 infinity is 0xc0||zeros, so it
never matched). FromValidUncompressedBytes now actually validates (was a silent
_ = UnmarshalBinary).
Identity-aggregate (defence in depth). AggregatePublicKeys now rejects an aggregate
that is the IDENTITY (point at infinity). Each input is a valid non-identity
subgroup key, and the subgroup is closed under addition — but the sum can still be
O when inputs sum to zero (the rogue-key shape pk + (-pk) = O). An identity
aggregate public key makes Verify trivially accept the identity signature (a forgery
enabler). PoP at registration already blocks an attacker contributing an
unpossessed key, but the verifier must not depend on that everywhere: purego
result.Validate() and cgo out.KeyValidate() fail the aggregate closed. Regression
(agg_identity_reject_test.go): pk + -pk must error; proven to FAIL without the guard
(returns a usable identity key); a legitimate two-key aggregate still succeeds.
Full bls suite green on both backends (CGO_ENABLED=0 and =1).
Symmetric with the isIdentityG1 pubkey guard in Verify. The canonical
compressed G2 identity (0xc0 || zeros) is a well-formed encoding both
deserializers accepted:
- purego (CIRCL): G2.SetBytes returns at the isInfinity branch BEFORE
IsOnG2 — add an explicit g.IsIdentity() reject.
- blst: SigValidate(false) skipped the infinity check — switch to
SigValidate(true), which is is_inf(p)==false && in_g2(p) (rejects
infinity AND keeps the r-torsion subgroup check; strictly additive).
The identity sig does not forge against a real key, but admitting a
degenerate point into the verifier is an asymmetry worth closing.
TDD: TestSignatureFromBytes_RejectsIdentity — identity sig rejected; a
real sig still round-trips byte-identically and verifies. Proven to fail
without the guard.
The //go:build !cgo (CIRCL) SignatureFromBytes stored any length-96 non-zero
blob as a Signature with NO point validation, diverging from the CGO/blst path
(Uncompress + SigValidate(false)) and admitting garbage signatures into the
verifier. Parse the input through bls12381.G2.SetBytes, which decodes the
compressed point and calls IsOnG2() = isValidProjective && isOnCurve &&
isRTorsion (the prime-order r-torsion SUBGROUP check) before returning — the
exact analogue of blst SigValidate(false). PublicKeyFromCompressedBytes already
calls pk.Validate(); this restores symmetry on the signature side.
Adds a !cgo-tagged regression test proving the behavioral change: a non-G2
garbage blob the old byte-loop accepted is now rejected; real signatures still
round-trip; all-zero and wrong-length stay rejected. CIRCL exposes no public
API to mint an on-curve-non-subgroup point, so the subgroup leg is enforced by
SetBytes (verified at ecc/bls12381/g2.go:86) and source review.
Native-Go authority for the Lux VM state roots — the byte-for-byte
reference the GPU accelerators (CUDA/HIP/Metal/Vulkan/WGSL) match.
- crypto/hash/keccak.go: ComputeKeccak256{,Array} via
sha3.NewLegacyKeccak256 (Ethereum Keccak-256, 0x01 pad) — NOT
sha3.Sum256 (FIPS-202 SHA3, 0x06 pad), which would diverge from the
GPU kernels and split consensus.
- crypto/merkle: Root/LeafHash/NodeHash/EmptyRoot. leaf=keccak(0x00|d),
node=keccak(0x01|L|R), RFC-6962 lone-right promotion, keccak256("")
empty root. Shape depends only on leaf count -> bit-identical to the
gpu-kernels lux::merkle::merkle_root spec across all backends.
Reproduces the spec's 7 canonical KAT vectors byte-for-byte
(n=0,1,2,3,4,5,8). CGO_ENABLED=0 clean, pure-Go, luxfi deps only.
Computation + tests ONLY — consensus wiring (fill xvm StandardBlock.Root
+ executor activation gate) is the separate next phase.
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.
Add stub packages for GPU-accelerated cryptography that return
false for Available() to indicate GPU is not available:
- gpu/gpu.go: General GPU crypto operations stub
- pq/mlkem/gpu/gpu.go: ML-KEM GPU operations stub
These stubs allow the precompile package to compile without
requiring actual GPU hardware.
- Add VerifySignature method to ML-DSA and SLH-DSA public keys
- Keep Verify method for crypto.Signer compatibility
- Add documentation clarifying opts parameter is ignored
- cggmp21: CGGMP21 threshold ECDSA protocol implementation
- mpc: Multi-party computation account management
- aggregated: Signature aggregation managers for BLS, Corona, CGGMP21
- corona: Linkable ring signature implementation
These packages were previously in node/crypto and are now properly
located in the dedicated crypto repository.
- Fix 18 missing if conditions in ipa/bandersnatch/fr/element_test.go
- Fix missing if condition in ipa/bandersnatch/multiexp_test.go
- Fix missing if condition in slhdsa/optimization_test.go
- Fix ML-DSA nil pointer panic by adding default SignerOpts
- Fix ML-DSA test to properly validate randomized signing behavior
All syntax errors resolved. All ML-DSA tests passing.
Resolves panic in TestAllCryptoImplementations/ML-DSA/ML-DSA-44.
- Added high-performance BLST implementation for CGO builds
- Added gnark-crypto v0.18.0 fallback for non-CGO builds
- Single source of truth for BLS12-381 operations
- Automatic implementation selection based on build flags
- Fixed TestNewContractAddress by using b.Bytes() in RLP encoding
- Fixed TestSaveECDSA by updating PaddedBigBytes to use FillBytes
- Added GitHub Actions workflow for testing
- All crypto package tests now passing
- Created common types (Hash, Address) in crypto/common
- Created hexutil utilities for hex encoding/decoding
- Created math utilities for big integer operations
- Created minimal RLP encoder for crypto package needs
- Updated all imports from github.com/luxfi/geth to local packages
- All tests pass with no functionality changes
- Created utils package with common types (Address, Hash, Big1, Big0)
- Added utility functions (BytesToAddress, HexToAddress, CopyBytes, FromHex)
- Implemented math utilities (PaddedBigBytes, MustParseBig256)
- Created minimal RLP encoder for CreateAddress function
- Added hexutil functions for KZG4844 support
- Updated all imports to use local utils instead of geth
- All tests passing successfully
- Implemented proper public key aggregation using direct G1 point addition
- Created DirectPublicKey, DirectSignature, and DirectSecretKey types that
wrap the circl BLS12-381 G1/G2 types directly
- Fixed AggregatePublicKeys to properly add G1 points instead of just
returning the first key
- Added comprehensive tests for aggregation that verify multi-signature works
- Added TODO notes for VerifyProofOfPossession and SignProofOfPossession
to use different domain separation tags (DST) once we have better access
to the underlying circl private key representation
The implementation now properly supports BLS signature aggregation which is
required for the Lux warp/lp118 multi-signature verification.
All tests pass including multi-signature aggregation verification.