mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
luxfi/crypto becomes the single Go entry point for ALL Lux-family crypto. Every public function in this module now dispatches between three implementations through a runtime-selectable backend: - vanilla: pure-Go reference (always available) - cgo: native binding (blst, libsecp256k1, ckzg) where present - gpu: batch acceleration via github.com/luxfi/accel The dispatcher reads LUX_CRYPTO_BACKEND (auto|vanilla|cgo|gpu); auto picks the most capable backend the binary was compiled and linked with. New canonical packages: backend/ runtime backend selector (env + programmatic) internal/gpuhost/ accel session lifecycle, single per-process keccak/ Keccak-256 with batch GPU dispatch sha256/ SHA-256 with batch GPU dispatch sha3/ SHA3 / SHAKE family ripemd160/ RIPEMD-160 (Bitcoin/Lux address derivation) ed25519/ Ed25519 with batch GPU verify bn254/ canonical alias for bn256 (matches FIPS naming) modexp/ canonical alias for bigmodexp evm256/ EIP-196/197 precompile ABI wrappers poseidon/ Poseidon2 hash via gnark-crypto pedersen/ Pedersen commitments over BN254 ntt/ Number-Theoretic Transform reference polymul/ negacyclic polynomial multiplication Extended existing packages with batch GPU paths: bls/batch.go BatchVerify routes through accel.BLSVerifyBatch mldsa/batch.go BatchVerify (ML-DSA-65) via accel.DilithiumVerifyBatch mlkem/batch.go BatchEncapsulate / BatchDecapsulate via Kyber kernels secp256k1/batch.go BatchVerifySignature via accel.ECDSAVerifyBatch GPU dispatch is gated on (a) backend.Default(), (b) batch size threshold, and (c) accel.Available(). When any gate fails the call falls through to the vanilla CPU path; output is byte-identical. The legacy gpu/ stub is replaced with a thin probe surface (Available, Backend, Devices, Version) that delegates to the same gpuhost session. Tests show vanilla and gpu backends produce identical outputs across all batch entry points (-race clean). See AUDIT.md for the per-algorithm state matrix and honest gaps.
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package evm256
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
|
|
bn254 "github.com/consensys/gnark-crypto/ecc/bn254"
|
|
"github.com/consensys/gnark-crypto/ecc/bn254/fp"
|
|
)
|
|
|
|
// PointSize is the size of an encoded G1 point on the EVM ABI: 64 bytes
|
|
// (X || Y, big-endian, 32 bytes each).
|
|
const PointSize = 64
|
|
|
|
// PairingPairSize is the size of one (G1, G2) pair on the EVM ABI:
|
|
// 64 + 128 = 192 bytes.
|
|
const PairingPairSize = 192
|
|
|
|
var (
|
|
ErrShortInput = errors.New("evm256: input too short")
|
|
ErrInvalidPoint = errors.New("evm256: point not on curve")
|
|
ErrInvalidPairings = errors.New("evm256: pairing input length not a multiple of 192")
|
|
)
|
|
|
|
// Add implements the bn256Add precompile (EIP-196): adds two G1 points
|
|
// encoded as 64-byte X||Y blobs and returns the sum, also as 64 bytes.
|
|
//
|
|
// Per EIP-196, missing input bytes are treated as zero; bytes past
|
|
// 2*PointSize are ignored.
|
|
func Add(input []byte) ([]byte, error) {
|
|
a, err := decodeG1(zeroPad(input, 0, PointSize))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := decodeG1(zeroPad(input, PointSize, PointSize))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var sum bn254.G1Jac
|
|
sum.FromAffine(&a).AddMixed(&b)
|
|
var out bn254.G1Affine
|
|
out.FromJacobian(&sum)
|
|
return encodeG1(out), nil
|
|
}
|
|
|
|
// ScalarMul implements the bn256ScalarMul precompile (EIP-196): multiplies
|
|
// G1 point P by scalar k and returns the resulting point. P is 64 bytes,
|
|
// k is 32 bytes; total input 96 bytes.
|
|
func ScalarMul(input []byte) ([]byte, error) {
|
|
p, err := decodeG1(zeroPad(input, 0, PointSize))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scalar := new(big.Int).SetBytes(zeroPad(input, PointSize, 32))
|
|
var pJ bn254.G1Jac
|
|
pJ.FromAffine(&p)
|
|
pJ.ScalarMultiplication(&pJ, scalar)
|
|
var out bn254.G1Affine
|
|
out.FromJacobian(&pJ)
|
|
return encodeG1(out), nil
|
|
}
|
|
|
|
// PairingCheck implements the bn256Pairing precompile (EIP-197). The input
|
|
// is a flat list of (G1, G2) pairs. Returns 0x...01 if the multi-pairing
|
|
// equals 1 in GT, else 0x...00 (32 bytes total).
|
|
//
|
|
// Empty input is well-defined per the spec and returns 0x..01.
|
|
func PairingCheck(input []byte) ([]byte, error) {
|
|
if len(input)%PairingPairSize != 0 {
|
|
return nil, ErrInvalidPairings
|
|
}
|
|
out := make([]byte, 32)
|
|
n := len(input) / PairingPairSize
|
|
if n == 0 {
|
|
out[31] = 1
|
|
return out, nil
|
|
}
|
|
a := make([]bn254.G1Affine, n)
|
|
b := make([]bn254.G2Affine, n)
|
|
for i := 0; i < n; i++ {
|
|
off := i * PairingPairSize
|
|
var err error
|
|
a[i], err = decodeG1(input[off : off+PointSize])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b[i], err = decodeG2(input[off+PointSize : off+PairingPairSize])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
ok, err := bn254.PairingCheck(a, b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ok {
|
|
out[31] = 1
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// zeroPad returns input[off:off+n] zero-padded if shorter than off+n.
|
|
func zeroPad(input []byte, off, n int) []byte {
|
|
out := make([]byte, n)
|
|
if off >= len(input) {
|
|
return out
|
|
}
|
|
end := off + n
|
|
if end > len(input) {
|
|
end = len(input)
|
|
}
|
|
copy(out, input[off:end])
|
|
return out
|
|
}
|
|
|
|
func decodeG1(buf []byte) (bn254.G1Affine, error) {
|
|
var p bn254.G1Affine
|
|
if len(buf) != PointSize {
|
|
return p, ErrShortInput
|
|
}
|
|
var x, y fp.Element
|
|
x.SetBytes(buf[0:32])
|
|
y.SetBytes(buf[32:64])
|
|
p.X = x
|
|
p.Y = y
|
|
if !p.IsOnCurve() && !(x.IsZero() && y.IsZero()) {
|
|
return bn254.G1Affine{}, ErrInvalidPoint
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func decodeG2(buf []byte) (bn254.G2Affine, error) {
|
|
var p bn254.G2Affine
|
|
if len(buf) != 128 {
|
|
return p, ErrShortInput
|
|
}
|
|
// EVM G2 encoding is (X.A1, X.A0, Y.A1, Y.A0) per EIP-197.
|
|
var x1, x0, y1, y0 fp.Element
|
|
x1.SetBytes(buf[0:32])
|
|
x0.SetBytes(buf[32:64])
|
|
y1.SetBytes(buf[64:96])
|
|
y0.SetBytes(buf[96:128])
|
|
p.X.A0 = x0
|
|
p.X.A1 = x1
|
|
p.Y.A0 = y0
|
|
p.Y.A1 = y1
|
|
if !p.IsOnCurve() && !(x0.IsZero() && x1.IsZero() && y0.IsZero() && y1.IsZero()) {
|
|
return bn254.G2Affine{}, ErrInvalidPoint
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func encodeG1(p bn254.G1Affine) []byte {
|
|
out := make([]byte, PointSize)
|
|
xBytes := p.X.Bytes()
|
|
yBytes := p.Y.Bytes()
|
|
copy(out[0:32], xBytes[:])
|
|
copy(out[32:64], yBytes[:])
|
|
return out
|
|
}
|