zeekay 9a71af427f bls: harden the deserialization + aggregation boundary (HIGH-1, RESIDUAL-C, identity-aggregate)
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).
2026-06-22 22:26:49 -07:00
2025-12-27 03:01:07 -08:00
2025-12-12 19:49:01 -08:00
2025-12-12 19:49:01 -08:00
2025-12-27 04:19:14 -08:00
2025-12-11 03:04:56 +00:00
2025-12-11 03:04:56 +00:00
2025-12-27 18:12:27 -08:00
2025-12-12 19:49:01 -08:00
2025-08-03 01:35:05 +00:00
2025-07-25 20:12:57 -05:00
2025-08-15 19:47:47 -05:00
2025-08-15 19:47:47 -05:00
2025-12-12 19:49:01 -08:00
2025-08-15 19:47:47 -05:00

Lux Crypto

Cryptographic primitives for the Lux Network -- post-quantum signatures, key encapsulation, BLS aggregation, threshold signing, ring signatures, and EVM-compatible secp256k1.

go get github.com/luxfi/crypto

Architecture

luxfi/crypto is the cryptographic foundation for all Lux software. It provides both classical and post-quantum primitives, with automatic CGO acceleration where available (blst for BLS, circl for lattice schemes). The pure-Go fallback path requires no C compiler.

Post-Quantum (NIST FIPS 203/204/205)

Package Algorithm Standard Security Key Sizes
mldsa/ ML-DSA FIPS 204 128/192/256-bit (Levels 2/3/5) 44: 1312/2560 B, 65: 1952/4032 B, 87: 2592/4896 B
mlkem/ ML-KEM FIPS 203 128/192/256-bit 512: 800/1632 B, 768: 1184/2400 B, 1024: 1568/3168 B
slhdsa/ SLH-DSA (FIPS 205, formerly SPHINCS+) FIPS 205 128/192/256-bit (12 variants) SHA2/SHAKE, fast/small tradeoff
pq/ Unified PQ interface -- Wraps mldsa, mlkem, slhdsa Mode selection at runtime

ML-DSA and ML-KEM wrap Cloudflare's circl with ergonomic key serialization. SLH-DSA provides hash-based signatures as a conservative fallback (no lattice assumptions).

Classical

Package Algorithm Use
bls/ BLS12-381 (G1 keys, G2 signatures) Consensus signatures, aggregation, proof-of-possession
secp256k1/ secp256k1 ECDSA EVM transaction signing, Ethereum compatibility
secp256r1/ P-256 ECDSA TLS, WebAuthn, FIDO2
ecies/ ECIES (secp256k1) Asymmetric encryption for Ethereum-compatible keys

Threshold and Multi-Party

Package Protocol Use
threshold/ Threshold signature framework Interface + registry for pluggable threshold schemes
threshold/bls/ BLS threshold signatures t-of-n BLS signing for consensus
cggmp21/ CGGMP21 (ECDSA threshold) MPC key generation and signing, Paillier commitments

Advanced Constructions

Package Construction Use
ring/ Ring signatures (LSAG + lattice-based) Unlinkable signer anonymity
lamport/ Lamport one-time signatures Hash-based PQ signatures (stateful)
hpke/ Hybrid Public Key Encryption ML-KEM + X25519 hybrid, KEM factory
kem/ KEM abstraction ML-KEM, X25519, hybrid combiner
aead/ AEAD ciphers AES-256-GCM, ChaCha20-Poly1305
kdf/ Key derivation HKDF, SLIP-10 HD derivation
verkle/ Verkle tree commitments State proof compression
kzg4844/ KZG commitments (EIP-4844) Blob transaction proofs
ipa/ Inner product arguments Verkle proof backend

Infrastructure

Package Purpose
common/ Address, Hash types (20-byte, 32-byte)
hash/, hashing/ Keccak256, SHA256, RIPEMD160, Blake2b
rlp/ RLP encoding (Ethereum wire format)
cb58/ CB58 encoding (Lux address format)
cert/ TLS certificate management for node identity
signer/ Transaction signing abstraction
sign/ Signature scheme registry
secret/ Secret-safe memory operations (zeroing, constant-time)
address/ Multi-chain address derivation
gpu/ GPU-accelerated modular arithmetic bindings
bindings/ C/Rust FFI exports

BLS Signatures

import "github.com/luxfi/crypto/bls"

sk, _ := bls.NewSecretKey()
pk := bls.PublicFromSecretKey(sk)

sig := bls.Sign(sk, []byte("block hash"))
valid := bls.Verify(pk, sig, []byte("block hash"))

// Aggregation
aggSig, _ := bls.AggregateSignatures(sig1, sig2, sig3)
aggPK, _ := bls.AggregatePublicKeys(pk1, pk2, pk3)
valid = bls.Verify(aggPK, aggSig, msg)

Post-Quantum Signatures (ML-DSA)

import "github.com/luxfi/crypto/mldsa"

sk, pk, _ := mldsa.GenerateKey(mldsa.MLDSA87)  // NIST Level 5
sig, _ := sk.Sign(rand.Reader, data, nil)
valid := pk.VerifySignature(data, sig)

Post-Quantum Key Encapsulation (ML-KEM)

import "github.com/luxfi/crypto/mlkem"

sk, pk, _ := mlkem.GenerateKey(mlkem.MLKEM1024)  // NIST Level 5
ciphertext, sharedSecret, _ := pk.Encapsulate()
recovered, _ := sk.Decapsulate(ciphertext)
// sharedSecret == recovered (32 bytes, use as AES key)

Hybrid HPKE

import "github.com/luxfi/crypto/hpke"

// ML-KEM-1024 + X25519 hybrid
suite := hpke.NewHybridSuite()
enc, ct, _ := suite.Seal(recipientPK, plaintext, aad)
pt, _ := suite.Open(recipientSK, enc, ct, aad)

Testing

go test ./...          # 382 test functions
go test -bench=. ./... # benchmarks

Compiled test binaries are provided for quick verification:

  • bls.test -- BLS signature tests
  • mldsa.test -- ML-DSA tests
  • mlkem.test -- ML-KEM tests
  • slhdsa.test -- SLH-DSA tests
  • crypto.test -- Core crypto tests

Papers

References

  • NIST FIPS 203: ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism)
  • NIST FIPS 204: ML-DSA (Module-Lattice-Based Digital Signature Algorithm)
  • NIST FIPS 205: SLH-DSA (Stateless Hash-Based Digital Signature Algorithm)
  • Cloudflare circl -- underlying lattice implementations
  • BLS12-381 -- pairing-friendly curve specification

License

Lux Ecosystem License v1.2. See LICENSE.

S
Description
Core cryptographic primitives for Lux: high-performance hash functions (SHA-2/3), symmetric ciphers, ECDSA & BLS signatures, HKDF key derivation, and lattice-based post-quantum schemes.
Readme
167 MiB
Languages
C 48.2%
Go 41.3%
Rust 4%
Assembly 1.3%
Sage 0.9%
Other 4.2%