bls12381: route G1MSM through luxfi/accel v1.0.8 (typed Curve enum)

g1MSM now attempts accelcrypto.MSM(CurveBLS12_381, scalars, points) before
falling back to gnark-crypto's MultiExp. When -tags=lux_crypto_native is
set the accel path links luxcpp/crypto v1.4.1's gpukit_multi_pippenger_cpu
(first-party Fp384 + Bernstein-Lange G1 Jacobian + signed-digit Pippenger,
zero blst). Without the tag accel returns ErrUnsupported and we use the
existing gnark-crypto path -- byte-equivalent to today's behaviour.

Validation (on-curve + in-subgroup) still runs through gnark-crypto's
decodeG1, so the precompile contract is unchanged: bad inputs return the
same error class as before.

Closes audit a5c7257e718ca0d94 -- the GPU-dispatch hole on BLS12-381 G1
MSM is now backed by a real CPU implementation through the accel facade.

99/99 EIP-2537 KAT pass (G1Add, G1Mul, G1MSM, G2Add, G2Mul, G2MSM, Pairing).
This commit is contained in:
2026-04-29 01:06:50 -07:00
parent e4123415d5
commit f423807c3c
3 changed files with 81 additions and 3 deletions
+78
View File
@@ -23,6 +23,7 @@ import (
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fp"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
accelcrypto "github.com/luxfi/accel/ops/crypto"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompile/contract"
)
@@ -246,6 +247,14 @@ func (o *blsOperations) g1MSM(input []byte, suppliedGas uint64) ([]byte, uint64,
scalars[i] = s
}
// Route through accel when its native CPU/GPU path is available; on
// ErrUnsupported (no cgo/native lib linked) fall back to gnark-crypto's
// MultiExp. The accel path uses the first-party luxcpp/crypto Pippenger
// MSM via gpukit_multi_pippenger when -tags=lux_crypto_native is set.
if result, ok := g1MSMAccel(points, scalars); ok {
return encodeG1(result), gas, nil
}
var result bls12381.G1Affine
_, err = result.MultiExp(points, scalars, ecc.MultiExpConfig{})
if err != nil {
@@ -254,6 +263,75 @@ func (o *blsOperations) g1MSM(input []byte, suppliedGas uint64) ([]byte, uint64,
return encodeG1(&result), gas, nil
}
// g1MSMAccel attempts the BLS12-381 G1 MSM through luxfi/accel. Returns
// (result, true) on success or (nil, false) when accel cannot handle the
// request (not linked, unsupported, or runtime error -- in which case the
// caller falls back to gnark-crypto MultiExp).
//
// Wire conversion: gpukit accepts 96-byte BE x||y per point (48 bytes each).
// gnark-crypto G1Affine.X / .Y are already canonical fp.Element values, so
// the BE byte serialisation matches gpukit's expected ABI exactly. The
// result is the same wire format and is decoded back into a G1Affine.
func g1MSMAccel(points []bls12381.G1Affine, scalars []fr.Element) (*bls12381.G1Affine, bool) {
const (
gpukitPointSize = 96 // 48-byte BE x || 48-byte BE y
gpukitScalarSize = 32 // LE canonical
fpSize = 48
)
n := len(points)
if n == 0 || n != len(scalars) {
return nil, false
}
scalarBufs := make([][]byte, n)
pointBufs := make([][]byte, n)
for i := 0; i < n; i++ {
s := make([]byte, gpukitScalarSize)
// fr.Element.Marshal returns BE; gpukit expects LE.
bigBytes := scalars[i].Bytes()
for j := 0; j < gpukitScalarSize; j++ {
s[j] = bigBytes[gpukitScalarSize-1-j]
}
scalarBufs[i] = s
p := make([]byte, gpukitPointSize)
if !points[i].IsInfinity() {
xb := points[i].X.Bytes()
yb := points[i].Y.Bytes()
copy(p[:fpSize], xb[:])
copy(p[fpSize:], yb[:])
}
pointBufs[i] = p
}
out, err := accelcrypto.MSM(accelcrypto.CurveBLS12_381, scalarBufs, pointBufs)
if err != nil {
return nil, false
}
if len(out) != gpukitPointSize {
return nil, false
}
var r bls12381.G1Affine
allZero := true
for _, b := range out {
if b != 0 {
allZero = false
break
}
}
if allZero {
// identity
return &r, true
}
r.X.SetBytes(out[:fpSize])
r.Y.SetBytes(out[fpSize:])
if !r.IsOnCurve() {
return nil, false
}
return &r, true
}
func (o *blsOperations) g2Add(input []byte, suppliedGas uint64) ([]byte, uint64, error) {
gas, err := contract.DeductGas(suppliedGas, GasG2Add)
if err != nil {
+1 -1
View File
@@ -9,7 +9,7 @@ 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.0.7
github.com/luxfi/accel v1.0.8
github.com/luxfi/ai v0.1.0
github.com/luxfi/crypto v1.18.1
github.com/luxfi/database v1.17.44
+2 -2
View File
@@ -75,8 +75,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
github.com/luxfi/accel v1.0.7 h1:ksHieAp50umwqxqgyHk9WiOmXM54kia3IEDb5H7FsM8=
github.com/luxfi/accel v1.0.7/go.mod h1:iZD3oxffiMEIT/KvzD8bgwC/cBn4AYlMW3QJpbRa4RE=
github.com/luxfi/accel v1.0.8 h1:NYvCa31W9/ay3RPyfG1mUjUPRTdp0O0Qrx0zIRl78Xk=
github.com/luxfi/accel v1.0.8/go.mod h1:XvgcJLYsLObbKlrOgd3E2OJx5Oy5c/HnDlIewjr+Y+c=
github.com/luxfi/ai v0.1.0 h1:PwTGob0GJivbdqNpUs82bvwcE8/qxyTokxfY7BZVPoo=
github.com/luxfi/ai v0.1.0/go.mod h1:lTuY32dGjEJUgVheYuanlt1O0jHj0AZ09sUSpRsmH6M=
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=