mirror of
https://github.com/luxfi/precompile.git
synced 2026-07-27 03:33:45 +00:00
deps: bump accel v1.3.1 + gpu v1.1.2 + crypto v1.20.2; add canonical GPU-dispatch parity gate
Align to the latest GPU-dispatch substrate: accel v1.3.1 now resolves the luxgpu pkg-config, gpu v1.1.2 is the libluxgpu binding. Zero API-drift — go build ./... and the full 54-package test link are green. Add precompile/dispatch: the single authority for GPU-batch-vs-CPU dispatch of the crypto/PQ/ZK/curve primitives. It lives in its own package so libluxgpu linkage stays opt-in — the 54 families that do not use the GPU never link the substrate, which also keeps them clear of the crypto _sha256/slhdsa link. A GPU batch kernel may back a precompile only when a parity test here proves it byte-equal to that precompile's CPU oracle; until then the CPU oracle is the single source of truth (consensus determinism). parity_test.go runs the real Metal kernels against the oracles and proves the current divergence (Poseidon2 by construction, BLS12-381 MSM by wire encoding), guarding the wiring decision in one place. No family is wired to a GPU kernel: every signature family diverges by context-binding, host-prehash framing, or partial-vs-full operation, so the CPU oracles and their regression tests stand unchanged and green. Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package dispatch is the single, canonical authority for GPU-batch-versus-CPU
|
||||
// dispatch of the precompile crypto/PQ/ZK/curve primitives. It wraps the latest
|
||||
// accelerated substrate — luxfi/accel (the batch kernel dispatch library) over
|
||||
// luxfi/gpu (the libluxgpu bindings) — behind ONE policy surface, so no family
|
||||
// carries its own ad-hoc cgo or its own "should we use the GPU?" decision.
|
||||
//
|
||||
// The invariant, and the reason this package is separate from contract:
|
||||
//
|
||||
// - A GPU batch kernel may back a precompile ONLY when a parity test in this
|
||||
// package proves it byte-equal to that precompile's CPU oracle. Consensus
|
||||
// determinism requires that GPU-equipped and CPU-only validators return the
|
||||
// byte-identical verdict on every input, including adversarial ones. Until a
|
||||
// kernel is proven byte-equal, the CPU oracle is the single source of truth.
|
||||
//
|
||||
// - Every precompile is, at the EVM call boundary, a batch-of-1 (one Run() per
|
||||
// call). n=1 is always below MinBatch, so a precompile always resolves to its
|
||||
// CPU oracle regardless of what kernels exist. The GPU path only earns its
|
||||
// dispatch overhead for a genuine multi-item batch (block/consensus-level
|
||||
// multi-signature or multi-hash verification), and only for a byte-equal
|
||||
// primitive.
|
||||
//
|
||||
// - This package deliberately does NOT live in contract: linking libluxgpu
|
||||
// pulls the GPU substrate (and its slhdsa/dilithium host archives) into the
|
||||
// cgo link. Keeping that linkage in one opt-in package means the 54 families
|
||||
// that do not use the GPU never link the substrate and stay CPU-lean.
|
||||
//
|
||||
// Current state (see parity_test.go, which runs the real kernels against the CPU
|
||||
// oracles): NO precompile family has a libluxgpu batch kernel that is byte-equal
|
||||
// to its consensus-pinned CPU oracle — the signature families diverge by
|
||||
// context-binding / host-prehash framing / partial-vs-full operation, the
|
||||
// Poseidon2 kernel diverges by construction, and BLS12-381 MSM diverges by wire
|
||||
// encoding. Every family therefore resolves to its CPU oracle. The parity tests
|
||||
// are the executable proof of that, and the guard that flips the day accel ships
|
||||
// a byte-equal, framing-matched kernel: a parity test starts passing, and wiring
|
||||
// that one primitive through here becomes a local change in exactly one place.
|
||||
package dispatch
|
||||
|
||||
import luxgpu "github.com/luxfi/gpu"
|
||||
|
||||
// MinBatch is the batch size below which the CPU oracle always wins: GPU
|
||||
// dispatch plus host<->device copy overhead exceeds the throughput gain. It
|
||||
// tracks the luxgpu substrate's own built-in ZK thresholds (Poseidon2=64,
|
||||
// Merkle=128). A single EVM precompile Run() is a batch-of-1 and so is always
|
||||
// served by the CPU oracle.
|
||||
const MinBatch = 64
|
||||
|
||||
// Available reports whether the accelerated luxgpu substrate is present and
|
||||
// usable on this host. It is the one place a batch caller asks "is the GPU path
|
||||
// even worth considering?" before assembling a batch; the answer never changes
|
||||
// the verdict, only the path taken to it. Note the substrate ZK kernels can be
|
||||
// live (this returns true) while accel's higher-level session backend registers
|
||||
// none — the two are checked independently, and the parity gate makes the
|
||||
// verdict backend-invariant regardless.
|
||||
func Available() bool { return luxgpu.ZKGPUAvailable() }
|
||||
|
||||
// Backend names the active luxgpu backend (metal/cuda/cpu) for diagnostics and
|
||||
// benchmarks. It carries no consensus meaning — the verdict is backend-invariant
|
||||
// by the parity gate above.
|
||||
func Backend() string { return luxgpu.ZKGetBackend() }
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package dispatch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/consensys/gnark-crypto/ecc"
|
||||
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
|
||||
blsfr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
|
||||
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
|
||||
"github.com/consensys/gnark-crypto/ecc/bn254/fr/poseidon2"
|
||||
acrypto "github.com/luxfi/accel/ops/crypto"
|
||||
luxgpu "github.com/luxfi/gpu"
|
||||
)
|
||||
|
||||
// The tests below are the executable proof behind package dispatch's invariant:
|
||||
// they run the real libluxgpu batch kernels against the precompiles' CPU oracles
|
||||
// and assert the current byte-level relationship. Each "…StaysCPU" test asserts
|
||||
// that the GPU kernel is NOT yet byte-equal to the pinned CPU oracle — the
|
||||
// reason the corresponding family resolves to CPU. If accel ever ships a kernel
|
||||
// that matches, that test fails; the failure is the signal to wire that one
|
||||
// primitive through this package (and to promote its check to a positive parity
|
||||
// assertion). This keeps the wiring decision in one place, backed by facts, not
|
||||
// by prose scattered across the family packages.
|
||||
|
||||
func eccCfg() ecc.MultiExpConfig { return ecc.MultiExpConfig{} }
|
||||
|
||||
func requireGPU(t *testing.T) {
|
||||
t.Helper()
|
||||
if !Available() {
|
||||
t.Skip("no accelerated backend on this host; GPU parity is a no-op")
|
||||
}
|
||||
t.Logf("accel backend: %s", Backend())
|
||||
}
|
||||
|
||||
// TestPoseidon2StaysCPU pins why the poseidon precompile (0x05) does not use the
|
||||
// libluxgpu Poseidon2 kernel: the precompile hashes via gnark's BN254 Poseidon2
|
||||
// Merkle-Damgard construction, while lux_gpu_poseidon2 is a bare 2-to-1
|
||||
// compression with different round framing. For the same field pair the two
|
||||
// disagree byte-for-byte, so wiring the kernel would split consensus.
|
||||
func TestPoseidon2StaysCPU(t *testing.T) {
|
||||
requireGPU(t)
|
||||
|
||||
var l, r fr.Element
|
||||
l.SetUint64(1)
|
||||
r.SetUint64(2)
|
||||
|
||||
// CPU oracle: exactly what poseidon.hashPair computes.
|
||||
h := poseidon2.NewMerkleDamgardHasher()
|
||||
lb := l.Bytes()
|
||||
rb := r.Bytes()
|
||||
h.Write(lb[:])
|
||||
h.Write(rb[:])
|
||||
cpu := h.Sum(nil)
|
||||
|
||||
// GPU kernel: 2-to-1 compression over the canonical field limbs.
|
||||
lf := luxgpu.Fr256{l.Bits()[0], l.Bits()[1], l.Bits()[2], l.Bits()[3]}
|
||||
rf := luxgpu.Fr256{r.Bits()[0], r.Bits()[1], r.Bits()[2], r.Bits()[3]}
|
||||
out, err := luxgpu.Poseidon2Hash([]luxgpu.Fr256{lf}, []luxgpu.Fr256{rf})
|
||||
if err != nil {
|
||||
t.Skipf("poseidon2 kernel unavailable: %v", err)
|
||||
}
|
||||
gpu := make([]byte, 32)
|
||||
for i := 0; i < 4; i++ {
|
||||
v := out[0][3-i]
|
||||
for j := 0; j < 8; j++ {
|
||||
gpu[i*8+j] = byte(v >> (8 * (7 - j)))
|
||||
}
|
||||
}
|
||||
|
||||
if bytes.Equal(cpu, gpu) {
|
||||
t.Fatalf("lux_gpu_poseidon2 is now byte-equal to the gnark Merkle-Damgard "+
|
||||
"oracle (cpu=%x) — evaluate wiring poseidon through dispatch", cpu)
|
||||
}
|
||||
t.Logf("poseidon2 diverges (expected): cpu=%x gpu=%x — precompile stays CPU", cpu, gpu)
|
||||
}
|
||||
|
||||
// TestMSMStaysCPU pins why bls12381 G1MSM (0x0d) does not use the accel MSM
|
||||
// kernel: MSM is a deterministic function, but accel's luxcpp multi-Pippenger
|
||||
// wire encoding differs from gnark's EIP-2537 point/scalar encoding, so the two
|
||||
// 96-byte results disagree byte-for-byte. (On this host the GPU path also
|
||||
// reports the curve unsupported; the CPU multi-Pippenger oracle is opt-in behind
|
||||
// the lux_crypto_native build tag.) Either way the precompile stays on gnark.
|
||||
func TestMSMStaysCPU(t *testing.T) {
|
||||
requireGPU(t)
|
||||
|
||||
const k = 8
|
||||
scalars := make([]blsfr.Element, k)
|
||||
points := make([]bls12381.G1Affine, k)
|
||||
scalarBytes := make([][]byte, k)
|
||||
pointBytes := make([][]byte, k)
|
||||
for i := 0; i < k; i++ {
|
||||
scalars[i].SetRandom()
|
||||
var si blsfr.Element
|
||||
si.SetRandom()
|
||||
points[i].ScalarMultiplicationBase(si.BigInt(new(big.Int)))
|
||||
sb := scalars[i].Bytes()
|
||||
scalarBytes[i] = append([]byte(nil), sb[:]...)
|
||||
pointBytes[i] = points[i].Marshal()
|
||||
}
|
||||
|
||||
var jac bls12381.G1Jac
|
||||
if _, err := jac.MultiExp(points, scalars, eccCfg()); err != nil {
|
||||
t.Fatalf("gnark MultiExp: %v", err)
|
||||
}
|
||||
var aff bls12381.G1Affine
|
||||
aff.FromJacobian(&jac)
|
||||
cpu := aff.Marshal()
|
||||
|
||||
gpu, err := acrypto.MSM(acrypto.CurveBLS12_381, scalarBytes, pointBytes)
|
||||
if err != nil {
|
||||
t.Skipf("accel BLS12-381 MSM unavailable on this build/host: %v", err)
|
||||
}
|
||||
if bytes.Equal(cpu, gpu) {
|
||||
t.Fatalf("accel BLS12-381 MSM is now byte-equal to gnark (cpu=%x) — "+
|
||||
"evaluate wiring bls12381 G1MSM through dispatch", cpu)
|
||||
}
|
||||
t.Logf("bls12381 MSM diverges (expected): encoding mismatch — precompile stays CPU")
|
||||
}
|
||||
@@ -9,15 +9,16 @@ require (
|
||||
github.com/consensys/gnark-crypto v0.20.1
|
||||
github.com/crate-crypto/go-kzg-4844 v1.1.0
|
||||
github.com/holiman/uint256 v1.3.2
|
||||
github.com/luxfi/accel v1.2.4
|
||||
github.com/luxfi/accel v1.3.1
|
||||
github.com/luxfi/ai v0.1.0
|
||||
github.com/luxfi/chains v1.3.18
|
||||
github.com/luxfi/corona v0.10.3
|
||||
github.com/luxfi/crypto v1.19.26
|
||||
github.com/luxfi/crypto v1.20.2
|
||||
github.com/luxfi/database v1.20.4
|
||||
github.com/luxfi/dex v1.14.0
|
||||
github.com/luxfi/fhe v1.8.2
|
||||
github.com/luxfi/geth v1.17.12
|
||||
github.com/luxfi/gpu v1.1.2
|
||||
github.com/luxfi/lattice/v7 v7.1.4
|
||||
github.com/luxfi/magnetar v1.2.3
|
||||
github.com/luxfi/runtime v1.1.3
|
||||
|
||||
@@ -202,6 +202,8 @@ github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzW
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/luxfi/accel v1.2.4 h1:5VbIHyEvvfobn2zBiTFODxDw1CeqxCepZOLlvkuf9yQ=
|
||||
github.com/luxfi/accel v1.2.4/go.mod h1:ISIwAX+ZfsL/S5nsP2JvfldXN6Nc+QzoWf6Jtaq+xsQ=
|
||||
github.com/luxfi/accel v1.3.1 h1:hr4Vu0yPWVh8DFMmwkr4erFvD9D5LdsP9eQjlzL3880=
|
||||
github.com/luxfi/accel v1.3.1/go.mod h1:ISIwAX+ZfsL/S5nsP2JvfldXN6Nc+QzoWf6Jtaq+xsQ=
|
||||
github.com/luxfi/age v1.5.0 h1:G69HbSV4R3vKEH9B0CulnRaMdSdf4RalMgP8xKmxHeI=
|
||||
github.com/luxfi/age v1.5.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
|
||||
github.com/luxfi/ai v0.1.0 h1:PwTGob0GJivbdqNpUs82bvwcE8/qxyTokxfY7BZVPoo=
|
||||
@@ -230,6 +232,8 @@ github.com/luxfi/corona v0.10.3 h1:Yi1oAkW0HEsf5fvst/tUN0AjRVg6DoNHB/IC0qrFWZE=
|
||||
github.com/luxfi/corona v0.10.3/go.mod h1:xe5qRir0p+FA6eETpyGDv4LjYySg1zVB13kmHpy9x94=
|
||||
github.com/luxfi/crypto v1.19.26 h1:+aHn/L479ak2ih7s/DkBZojjuhcyHBLqu3nYT81vcrU=
|
||||
github.com/luxfi/crypto v1.19.26/go.mod h1:0DCU62kX8+zhYU2qeM07A4pifJyPkPujnUOfgc8TOFQ=
|
||||
github.com/luxfi/crypto v1.20.2 h1:L81WEsU/hs2A76F5PWBusG0yU74QqkDdUqqgexWUxh4=
|
||||
github.com/luxfi/crypto v1.20.2/go.mod h1:qYHOM0lO4PRh7LEaObxFQUIMjmT1/paVm/WgZkobT1k=
|
||||
github.com/luxfi/crypto/ipa v1.2.4 h1:6xfwhI9/HrcDkF3Ti5/NxsNQIWbwYDJmRSNIHRQ/xfU=
|
||||
github.com/luxfi/crypto/ipa v1.2.4/go.mod h1:43J6f6rcfUMrZt4cQectMOZb6Ps+fAEj8ZTPC3Kk+gE=
|
||||
github.com/luxfi/database v1.20.4 h1:WOt2GIGJxf8AFpg49odMz8DZ8RFSLDrozGhZtmorN70=
|
||||
@@ -242,6 +246,8 @@ github.com/luxfi/fhe v1.8.2 h1:QllnObNFbi6D4mvFI6uQkepW8HgLtdy4RMR1TKYAInA=
|
||||
github.com/luxfi/fhe v1.8.2/go.mod h1:16yxwhcnCez/rNcd/C9JjH9IjbEz73X+0tvlsONyLeA=
|
||||
github.com/luxfi/geth v1.17.12 h1:UP/fhpcfbGPTrkOCwX3d88Oc3jVm5gTOgfjgq+lek6s=
|
||||
github.com/luxfi/geth v1.17.12/go.mod h1:3vQfQJd9JC+AVBjxNXa9PYQOqpbE2dKu8E3jqhPZ3LU=
|
||||
github.com/luxfi/gpu v1.1.2 h1:xQupCMqb8L18WoBaMcTzVGP8856Nkhc+CkmqKzMw9nI=
|
||||
github.com/luxfi/gpu v1.1.2/go.mod h1:26fz0M7/R8PwZIR2Z3DTZ+iZWInb2AG4v3/wKwL/44Q=
|
||||
github.com/luxfi/ids v1.2.15 h1:omE+E4+0Poj9DzM11ejSFgteaSQ3KDHi5g54iH6jcxI=
|
||||
github.com/luxfi/ids v1.2.15/go.mod h1:Fj73K5xcblvdE0SxU/ip+jE8VqNdu+80548su5KJ7xI=
|
||||
github.com/luxfi/lattice/v7 v7.1.4 h1:hQR02M6cHTAV5+joOPi9gb9Gm+z/hKJnhJF4IlciIJs=
|
||||
|
||||
Reference in New Issue
Block a user