Follow-up to the main rename: caught README/PRIMER/LLM/MDX/Sol that survived the first pass. Live source greps clean now (audit output: /private/tmp/.../tasks/bs3iodq61.output → only build artifacts + vendor/ + binaries remain, which auto-regenerate).
12 KiB
Go 1.26 Modernization Analysis — luxfi/precompile
Analysis date: 2026-04-10 Go version: 1.26.1 Codebase: 72,578 lines across 137 Go files Test baseline: 5 failures (ed25519 panic, mldsa/mlkem build, go vet warnings)
1. Go Version Compatibility Matrix
| Repo | Current | Recommended | Action |
|---|---|---|---|
lux/precompile |
1.26.1 | 1.26.1 | None |
lux/evm |
1.26.1 | 1.26.1 | None |
lux/node |
1.26.1 | 1.26.1 | None |
liquidity/evm |
1.26.1 | 1.26.1 | None |
liquidity/dex |
1.26 | 1.26.1 | Bump to 1.26.1 |
liquidity/fhe |
1.26 | 1.26.1 | Bump to 1.26.1 |
Two repos are on 1.26 (no patch). Go 1.26.1 includes security fixes [1].
zoo/chains and hanzo/chains directories do not exist.
2. Critical: Consensus Safety Bug in pqcrypto
File: pqcrypto/contract.go:339
Severity: CRITICAL (chain-splitting)
// CURRENT — NON-DETERMINISTIC:
ciphertext, sharedSecret, err := pubKey.Encapsulate(rand.Reader)
rand.Reader produces different bytes on every validator. Different validators
will produce different ciphertexts for the same input, causing state divergence.
The hpke/contract.go handles this correctly with a deterministicReader that
derives pseudorandom bytes from a caller-provided seed via iterated SHA-256.
The mlkem/contract.go also avoids this by not passing an explicit reader.
Fix: Replace rand.Reader with a deterministic reader seeded from calldata,
identical to the pattern in hpke/contract.go:100-114. Or require a 32-byte
seed in the encapsulate calldata format and use newDeterministicReader(seed).
3. Actionable Improvements
3a. interface{} to any (30+ occurrences)
Go 1.18 introduced any as an alias for interface{}. The go fix tool's
any modernizer handles this automatically.
Files affected (partial list):
dex/pool_manager.go:858—args ...interface{}attestation/attestation.go:463,467,546— decode/encode helpersgraph/graphvm/types.go:31,37,48— all map/interface typesgraph/graph.go:27,30,175,259,320,351— query interfacesgraph/graphvm_client.go:48,84— client query methodsblake3/contract_test.go:18-19— mock state methods
Command: go fix -fix any ./...
3b. sort.Sort to slices.SortFunc (modules/registerer.go)
The moduleArray type in modules/module.go:25-37 exists solely to satisfy
sort.Interface. Replace with slices.SortFunc and delete the type.
Current (modules/module.go:25-37 + modules/registerer.go:257):
type moduleArray []Module
func (u moduleArray) Len() int { return len(u) }
func (u moduleArray) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
func (m moduleArray) Less(i, j int) bool { return bytes.Compare(...) < 0 }
sort.Sort(moduleArray(data))
After:
slices.SortFunc(data, func(a, b Module) int {
return bytes.Compare(a.Address.Bytes(), b.Address.Bytes())
})
Deletes 13 lines of boilerplate. Also applies to dex/state_export.go which
uses sort.Slice (can migrate to slices.SortFunc for consistency).
3c. rand.Read error checks (10 occurrences)
Since Go 1.24, crypto/rand.Read never returns an error [2]. Error checks on
rand.Read are dead code.
| File | Line | Current | After |
|---|---|---|---|
x25519/deep_test.go |
88 | _, err := rand.Read(scalar) |
rand.Read(scalar) |
x25519/deep_test.go |
112-113 | _, _ = rand.Read(...) |
rand.Read(...) |
curve25519/deep_test.go |
213 | _, err := rand.Read(buf[:]) |
rand.Read(buf[:]) |
xwing/contract_test.go |
130 | _, err := rand.Read(garbage) |
rand.Read(garbage) |
Corona/contract_test.go |
227 | if _, err := rand.Read(prfKey); err != nil |
rand.Read(prfKey) |
ed25519/security_test.go |
34,75 | _, _ = rand.Read(message) |
rand.Read(message) |
ring/security_test.go |
55,152 | _, _ = rand.Read(...) |
rand.Read(...) |
3d. runtime/secret for cryptographic erasure (Go 1.26 experimental)
Go 1.26 adds runtime/secret (GOEXPERIMENT=runtimesecret) which zeroes
registers and stack after a function returns [3]. Directly applicable to:
| Package | Hot path | Why |
|---|---|---|
frost/contract.go |
verifySchnorrSignature |
LiftX handles public key points |
pqcrypto/contract.go |
mlkemDecapsulate |
Private key in calldata |
pqcrypto/extended_contract.go |
mldsaSign, slhdsaSign |
Private key signing |
threshold/accel.go |
verifySingleSignature |
Key material during verify |
fhe/fhe_ops.go |
initTFHE |
Secret key generation |
Implementation: Wrap with secret.Do(func() { ... }) once
GOEXPERIMENT=runtimesecret is stable. Currently amd64+arm64 Linux only.
Risk: Experimental. Do not gate production builds on it. Use build tags:
//go:build goexperiment.runtimesecret
3e. crypto/hpke stdlib replacement (Go 1.26)
Go 1.26 added crypto/hpke implementing RFC 9180 [4]. Current code uses
github.com/cloudflare/circl/hpke.
Compatibility check:
| Feature | circl | stdlib crypto/hpke |
|---|---|---|
| P-256/P-384/P-521 KEMs | Yes | Yes |
| X25519 KEM | Yes | Yes |
| X25519+Kyber768 hybrid | Yes | Yes (post-quantum) |
| X-Wing hybrid | Yes | Needs verification |
| Deterministic Setup (custom rand) | Yes (sender.Setup accepts io.Reader) | Needs verification |
Blocker: The hpke/contract.go:381 passes nil to sender.Setup() which
circl interprets as "use default randomness". The deterministic reader is used
via a different code path. Must verify stdlib crypto/hpke accepts a custom
io.Reader for consensus-safe deterministic encryption before migrating.
Verdict: Investigate but do not migrate until the custom-reader API is
confirmed. If confirmed, drop cloudflare/circl (saves ~15MB of transitive
dependencies).
3f. go vet fixes (5 issues found)
blake3/deep_test.go:245 TestDeriveKey redeclared in this block
curve25519/deep_test.go:206 basepoint redeclared in this block
mlkem/deep_test.go:73 priv.Decapsulate undefined (API mismatch)
mldsa/deep_test.go:21 undefined: ContractAddress
pedersen/contract.go:273 unreachable code
These cause build failures in mldsa and mlkem packages and indicate stale
deep_test.go files that were not updated after API changes.
3g. bytes.Equal in tests — use require.Equal
28 occurrences of require.True(t, bytes.Equal(a, b), ...) in test files.
Replace with require.Equal(t, a, b) which provides better diff output on
failure.
Affected packages: babyjubjub, x25519, pasta, pedersen, poseidon,
curve25519, blake3.
4. Dependency Reductions
Can drop (with work)
| Dependency | Used by | Replacement | Savings |
|---|---|---|---|
cloudflare/circl |
hpke/ |
crypto/hpke (stdlib) |
~15MB transitive deps |
golang.org/x/crypto/curve25519 |
x25519/ |
None (see below) | Minimal |
Cannot drop
| Dependency | Reason |
|---|---|
luxfi/crypto/mlkem |
Wraps all 3 modes (512/768/1024). Stdlib only has 768+1024. Mode-based API differs. |
luxfi/crypto/mldsa |
No stdlib equivalent. ML-DSA (FIPS 204) not in Go stdlib. |
luxfi/crypto/slhdsa |
No stdlib equivalent. SLH-DSA (FIPS 205) not in Go stdlib. |
golang.org/x/crypto/curve25519 |
Stdlib crypto/ecdh wraps X25519 in high-level key API. Precompile needs raw X25519(scalar, point) which only x/crypto provides. |
consensys/gnark-crypto |
BLS12-381, BN254, Pasta curves, Poseidon hash. No stdlib equivalent. |
supranational/blst |
BLS12-381 via CGO. Performance-critical. |
zeebo/blake3 |
No stdlib Blake3. |
holiman/uint256 |
256-bit integer math for EVM. No stdlib equivalent. |
Free performance from Go 1.26 (already active)
- Green Tea GC: 10-40% lower GC overhead for marking/scanning [5]. Benefits all precompiles that allocate during execution (ZK verifier, DEX math).
- 30% faster cgo: Benefits
supranational/blst(BLS12-381) directly [6]. - Stack-allocated slices: Compiler allocates small slices on stack. Benefits
the many
make([]byte, 32)patterns in crypto precompiles.
5. Performance Wins
5a. PGO (Profile-Guided Optimization)
No PGO profile exists. Go 1.21+ supports PGO for 2-7% throughput improvement on hot paths, with some workloads seeing up to 15% [7].
Plan:
- Add benchmark suite covering hot paths (FROST verify, BLS aggregate, ZK Groth16 verify, DEX swap math, Poseidon hash)
- Run benchmarks with
-cpuprofile=default.pgo - Commit
default.pgoto repo root - Go toolchain automatically uses it on subsequent builds
Estimated gas savings: 2-7% reduction in wall-clock time for crypto precompiles translates to more accurate gas pricing (current gas costs are calibrated to unoptimized builds).
Hot paths for profiling (by test duration):
fhe: 71.8s (FHE is inherently slow; PGO helps most here)pqcrypto: 4.7sCorona: 4.8sslhdsa: 3.7szk: 3.8s
5b. Frost liftXCache improvement
frost/contract.go:147-182 uses a sync.RWMutex + map[[32]byte]curve.Point
bounded to 1024 entries with no eviction policy. When full, new keys are silently
dropped.
Improvement: Replace with sync.Map (better for read-heavy workloads, which
this is — validators reuse the same keys) or implement LRU eviction. The current
"stop caching when full" behavior means the 1025th unique public key gets zero
cache benefit permanently.
5c. SIMD (not ready)
Go 1.26 adds experimental simd/archsimd (GOEXPERIMENT=simd) for AMD64 only [8].
ARM64 SVE support is planned but not yet available. Since production nodes run on
both architectures, this is not actionable now. Revisit when ARM64 is supported.
Potential targets when ready:
pasta/contract.go— field element arithmeticposeidon/contract.go— sponge state permutationbls12381/contract.go— point addition/multiplicationdex/full_math.go— 256-bit multiplication
6. go fix Modernizer Batch
Go 1.26 ships 24 built-in modernizers [9]. Applicable ones:
| Modernizer | Occurrences | Risk |
|---|---|---|
any |
30+ | None (alias, identical semantics) |
slicessort |
1 (modules/) + 5 (dex/) |
None |
slicescontains |
Check needed | None |
forvar |
0 (already correct) | N/A |
rangeint |
Check needed | None |
Command: go fix -fix any,slicessort,slicescontains,rangeint ./...
Run go fix -diff ./... first to preview changes without applying them.
7. Existing Bugs Found During Analysis
| Issue | File | Severity |
|---|---|---|
Non-deterministic rand.Reader in encapsulate |
pqcrypto/contract.go:339 |
CRITICAL |
TestEdge_AllZeros panic |
ed25519/deep_test.go:45 |
Medium |
priv.Decapsulate undefined |
mlkem/deep_test.go:73 |
Medium (build fail) |
ContractAddress undefined |
mldsa/deep_test.go:21 |
Medium (build fail) |
TestDeriveKey redeclared |
blake3/deep_test.go:245 |
Low (build fail) |
basepoint redeclared |
curve25519/deep_test.go:206 |
Low (build fail) |
| Unreachable code | pedersen/contract.go:273 |
Low |
8. Summary of Recommendations
Priority order:
- FIX
pqcrypto/contract.go:339consensus bug (deterministic encapsulation) - FIX 5 go vet failures (build-breaking in mldsa, mlkem, blake3, curve25519)
- RUN
go fix -fix any,slicessort ./...(mechanical, zero-risk) - REMOVE
rand.Readerror checks (10 occurrences, dead code since Go 1.24) - REPLACE
sort.Sort(moduleArray(...))withslices.SortFunc(delete boilerplate) - GENERATE PGO profile from benchmark suite (2-7% throughput improvement)
- INVESTIGATE
crypto/hpkestdlib migration (potential circl dep removal) - PLAN
runtime/secretintegration (when GOEXPERIMENT stabilizes) - BUMP
liquidity/dexandliquidity/fhefrom Go 1.26 to 1.26.1
Sources
- [1] https://go.dev/doc/devel/release — Go release history
- [2] https://go.dev/doc/go1.24 —
crypto/rand.Readinfallible since 1.24 - [3] https://pkg.go.dev/runtime/secret — runtime/secret experimental package
- [4] https://pkg.go.dev/crypto/hpke — stdlib HPKE (RFC 9180)
- [5] https://github.com/golang/go/issues/73581 — Green Tea GC
- [6] https://go.dev/doc/go1.26 — Go 1.26 release notes (cgo 30% faster)
- [7] https://go.dev/doc/pgo — Profile-Guided Optimization
- [8] https://pkg.go.dev/simd/archsimd — experimental SIMD
- [9] https://go.dev/blog/gofix — go fix modernizers (Go 1.26)
- [10] https://pkg.go.dev/crypto/mlkem — stdlib ML-KEM (FIPS 203)