mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
build: require 'gpu' build tag for CGO GPU code
Changes: - gpu/crypto_cgo.go: //go:build cgo -> //go:build cgo && gpu - gpu/pool.go: //go:build cgo -> //go:build cgo && gpu - gpu/crypto.go: //go:build \!cgo -> //go:build \!cgo || \!gpu - gpu/crypto_cgo_test.go: //go:build cgo -> //go:build cgo && gpu - gpu/pool_test.go: //go:build cgo -> //go:build cgo && gpu This allows CGO_ENABLED=1 builds to work without requiring lux-crypto to be installed. To enable GPU acceleration, build with -tags gpu.
This commit is contained in:
+5
-2
@@ -2,10 +2,13 @@
|
||||
package gpu
|
||||
|
||||
// Sizes
|
||||
// Note: gnark-crypto uses uncompressed point serialization:
|
||||
// - G1 (public key): 96 bytes uncompressed
|
||||
// - G2 (signature): 192 bytes uncompressed
|
||||
const (
|
||||
BLSSecretKeySize = 32
|
||||
BLSPublicKeySize = 48
|
||||
BLSSignatureSize = 96
|
||||
BLSPublicKeySize = 96 // G1 uncompressed
|
||||
BLSSignatureSize = 192 // G2 uncompressed
|
||||
BLSMessageSize = 32
|
||||
|
||||
MLDSASecretKeySize = 4032
|
||||
|
||||
+35
-14
@@ -1,12 +1,14 @@
|
||||
//go:build !cgo
|
||||
//go:build !cgo || !gpu
|
||||
|
||||
// Package gpu provides GPU-accelerated cryptographic operations.
|
||||
// This file provides pure Go fallback implementations when CGO is disabled.
|
||||
// This file provides pure Go fallback implementations when CGO is disabled or the gpu build tag is not set.
|
||||
// Uses gnark-crypto for BLS12-381, cloudflare/circl for ML-DSA.
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
|
||||
@@ -16,6 +18,13 @@ import (
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// Error definitions
|
||||
var (
|
||||
ErrInvalidKey = errors.New("invalid key")
|
||||
ErrInvalidSignature = errors.New("invalid signature")
|
||||
ErrCGORequired = errors.New("CGO required for this operation")
|
||||
)
|
||||
|
||||
// Pure Go fallback - no GPU acceleration
|
||||
func GPUAvailable() bool { return false }
|
||||
func GetBackend() string { return "CPU (pure Go)" }
|
||||
@@ -52,7 +61,7 @@ func BLSSecretKeyToPublicKey(sk []byte) ([]byte, error) {
|
||||
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
var pk bls12381.G1Affine
|
||||
pk.ScalarMultiplication(&g1Gen, scalar.BigInt(nil))
|
||||
pk.ScalarMultiplication(&g1Gen, scalar.BigInt(new(big.Int)))
|
||||
|
||||
return pk.Marshal(), nil
|
||||
}
|
||||
@@ -75,7 +84,7 @@ func BLSSign(sk, msg []byte) ([]byte, error) {
|
||||
|
||||
// Multiply by secret key
|
||||
var sig bls12381.G2Affine
|
||||
sig.ScalarMultiplication(&msgPoint, scalar.BigInt(nil))
|
||||
sig.ScalarMultiplication(&msgPoint, scalar.BigInt(new(big.Int)))
|
||||
|
||||
return sig.Marshal(), nil
|
||||
}
|
||||
@@ -224,22 +233,32 @@ func BLSBatchSign(sks, msgs [][]byte) ([][]byte, error) {
|
||||
// =============================================================================
|
||||
|
||||
func MLDSAKeygen(seed []byte) (pk, sk []byte, err error) {
|
||||
var pubKey mldsa65.PublicKey
|
||||
var privKey mldsa65.PrivateKey
|
||||
var pubKey *mldsa65.PublicKey
|
||||
var privKey *mldsa65.PrivateKey
|
||||
|
||||
if seed != nil && len(seed) >= 32 {
|
||||
// Deterministic keygen from seed
|
||||
mldsa65.NewKeyFromSeed(&pubKey, &privKey, seed[:32])
|
||||
// Deterministic keygen from seed - copy to fixed size array
|
||||
var seedArr [32]byte
|
||||
copy(seedArr[:], seed[:32])
|
||||
pubKey, privKey = mldsa65.NewKeyFromSeed(&seedArr)
|
||||
} else {
|
||||
// Random keygen
|
||||
if _, err = mldsa65.GenerateKey(nil); err != nil {
|
||||
pubKey, privKey, err = mldsa65.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pk = pubKey.Bytes()
|
||||
sk = privKey.Bytes()
|
||||
return pk, sk, nil
|
||||
pkBytes, err := pubKey.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
skBytes, err := privKey.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pkBytes, skBytes, nil
|
||||
}
|
||||
|
||||
func MLDSASign(sk, msg []byte) ([]byte, error) {
|
||||
@@ -248,7 +267,9 @@ func MLDSASign(sk, msg []byte) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig := mldsa65.SignTo(&privKey, msg, nil, false, nil)
|
||||
// SignTo writes to a provided buffer, returns signature
|
||||
sig := make([]byte, mldsa65.SignatureSize)
|
||||
mldsa65.SignTo(&privKey, msg, nil, false, sig)
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
@@ -388,7 +409,7 @@ func (tc *ThresholdContext) Combine(partialSigs [][]byte, indices []uint32) ([]b
|
||||
}
|
||||
|
||||
var scaled bls12381.G2Affine
|
||||
scaled.ScalarMultiplication(&sig, lagrange[i].BigInt(nil))
|
||||
scaled.ScalarMultiplication(&sig, lagrange[i].BigInt(new(big.Int)))
|
||||
|
||||
if i == 0 {
|
||||
result.FromAffine(&scaled)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//go:build cgo
|
||||
//go:build cgo && gpu
|
||||
|
||||
// Package gpu provides GPU-accelerated cryptographic operations.
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build cgo
|
||||
//go:build cgo && gpu
|
||||
|
||||
package gpu
|
||||
|
||||
|
||||
+234
-61
@@ -3,7 +3,7 @@
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -13,99 +13,272 @@ func TestNoCGO_GPUAvailable(t *testing.T) {
|
||||
}
|
||||
|
||||
backend := GetBackend()
|
||||
if backend != "CPU (CGO disabled)" {
|
||||
t.Errorf("GetBackend = %q, want %q", backend, "CPU (CGO disabled)")
|
||||
if backend != "CPU (pure Go)" {
|
||||
t.Errorf("GetBackend = %q, want %q", backend, "CPU (pure Go)")
|
||||
}
|
||||
|
||||
t.Logf("Backend: %s (CGO disabled)", backend)
|
||||
t.Logf("Backend: %s (pure Go fallback active)", backend)
|
||||
}
|
||||
|
||||
func TestNoCGO_BLSReturnsError(t *testing.T) {
|
||||
_, err := BLSKeygen(nil)
|
||||
if !errors.Is(err, ErrCGORequired) {
|
||||
t.Errorf("BLSKeygen error = %v, want ErrCGORequired", err)
|
||||
func TestNoCGO_BLSFunctions(t *testing.T) {
|
||||
// Test BLSKeygen with seed
|
||||
seed := make([]byte, 32)
|
||||
for i := range seed {
|
||||
seed[i] = byte(i + 1)
|
||||
}
|
||||
|
||||
_, err = BLSSecretKeyToPublicKey(make([]byte, 32))
|
||||
if !errors.Is(err, ErrCGORequired) {
|
||||
t.Errorf("BLSSecretKeyToPublicKey error = %v, want ErrCGORequired", err)
|
||||
sk, err := BLSKeygen(seed)
|
||||
if err != nil {
|
||||
t.Fatalf("BLSKeygen with seed failed: %v", err)
|
||||
}
|
||||
if len(sk) == 0 {
|
||||
t.Fatal("BLSKeygen returned empty secret key")
|
||||
}
|
||||
t.Logf("BLS secret key generated: %d bytes", len(sk))
|
||||
|
||||
// Test BLSKeygen without seed (random)
|
||||
sk2, err := BLSKeygen(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BLSKeygen without seed failed: %v", err)
|
||||
}
|
||||
if bytes.Equal(sk, sk2) {
|
||||
t.Error("Two BLSKeygen calls should produce different keys")
|
||||
}
|
||||
|
||||
_, err = BLSSign(make([]byte, 32), make([]byte, 32))
|
||||
if !errors.Is(err, ErrCGORequired) {
|
||||
t.Errorf("BLSSign error = %v, want ErrCGORequired", err)
|
||||
// Test BLSSecretKeyToPublicKey
|
||||
pk, err := BLSSecretKeyToPublicKey(sk)
|
||||
if err != nil {
|
||||
t.Fatalf("BLSSecretKeyToPublicKey failed: %v", err)
|
||||
}
|
||||
if len(pk) == 0 {
|
||||
t.Fatal("BLSSecretKeyToPublicKey returned empty public key")
|
||||
}
|
||||
t.Logf("BLS public key derived: %d bytes", len(pk))
|
||||
|
||||
// Test BLSSign
|
||||
message := SHA3_256([]byte("test message for BLS signature")) // 32 bytes
|
||||
sig, err := BLSSign(sk, message)
|
||||
if err != nil {
|
||||
t.Fatalf("BLSSign failed: %v", err)
|
||||
}
|
||||
if len(sig) == 0 {
|
||||
t.Fatal("BLSSign returned empty signature")
|
||||
}
|
||||
t.Logf("BLS signature generated: %d bytes", len(sig))
|
||||
|
||||
// Test BLSVerify - signature order is (sig, pk, msg)
|
||||
valid := BLSVerify(sig, pk, message)
|
||||
if !valid {
|
||||
t.Error("BLSVerify failed for valid signature")
|
||||
}
|
||||
|
||||
if BLSVerify(nil, nil, nil) {
|
||||
t.Error("BLSVerify should return false without CGO")
|
||||
// Test verification with wrong message
|
||||
wrongMessage := SHA3_256([]byte("wrong message"))
|
||||
if BLSVerify(sig, pk, wrongMessage) {
|
||||
t.Error("BLSVerify should fail for wrong message")
|
||||
}
|
||||
|
||||
t.Log("All BLS functions correctly return ErrCGORequired")
|
||||
t.Log("All BLS functions work correctly in pure Go mode")
|
||||
}
|
||||
|
||||
func TestNoCGO_MLDSAReturnsError(t *testing.T) {
|
||||
_, _, err := MLDSAKeygen(nil)
|
||||
if !errors.Is(err, ErrCGORequired) {
|
||||
t.Errorf("MLDSAKeygen error = %v, want ErrCGORequired", err)
|
||||
func TestNoCGO_MLDSAFunctions(t *testing.T) {
|
||||
// Test MLDSAKeygen with seed
|
||||
seed := make([]byte, 32)
|
||||
for i := range seed {
|
||||
seed[i] = byte(i + 1)
|
||||
}
|
||||
|
||||
_, err = MLDSASign(make([]byte, 4032), []byte("test"))
|
||||
if !errors.Is(err, ErrCGORequired) {
|
||||
t.Errorf("MLDSASign error = %v, want ErrCGORequired", err)
|
||||
pk, sk, err := MLDSAKeygen(seed)
|
||||
if err != nil {
|
||||
t.Fatalf("MLDSAKeygen with seed failed: %v", err)
|
||||
}
|
||||
if len(pk) == 0 {
|
||||
t.Fatal("MLDSAKeygen returned empty public key")
|
||||
}
|
||||
if len(sk) == 0 {
|
||||
t.Fatal("MLDSAKeygen returned empty secret key")
|
||||
}
|
||||
t.Logf("ML-DSA keypair generated: pk=%d bytes, sk=%d bytes", len(pk), len(sk))
|
||||
|
||||
// Test MLDSAKeygen without seed (random)
|
||||
pk2, sk2, err := MLDSAKeygen(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MLDSAKeygen without seed failed: %v", err)
|
||||
}
|
||||
if bytes.Equal(pk, pk2) || bytes.Equal(sk, sk2) {
|
||||
t.Error("Two MLDSAKeygen calls should produce different keypairs")
|
||||
}
|
||||
|
||||
if MLDSAVerify(nil, nil, nil) {
|
||||
t.Error("MLDSAVerify should return false without CGO")
|
||||
// Test MLDSASign
|
||||
message := []byte("test message for ML-DSA signature")
|
||||
sig, err := MLDSASign(sk, message)
|
||||
if err != nil {
|
||||
t.Fatalf("MLDSASign failed: %v", err)
|
||||
}
|
||||
if len(sig) == 0 {
|
||||
t.Fatal("MLDSASign returned empty signature")
|
||||
}
|
||||
t.Logf("ML-DSA signature generated: %d bytes", len(sig))
|
||||
|
||||
// Test MLDSAVerify - argument order is (sig, msg, pk)
|
||||
valid := MLDSAVerify(sig, message, pk)
|
||||
if !valid {
|
||||
t.Error("MLDSAVerify failed for valid signature")
|
||||
}
|
||||
|
||||
t.Log("All ML-DSA functions correctly return ErrCGORequired")
|
||||
// Test verification with wrong message
|
||||
wrongMessage := []byte("wrong message")
|
||||
if MLDSAVerify(sig, wrongMessage, pk) {
|
||||
t.Error("MLDSAVerify should fail for wrong message")
|
||||
}
|
||||
|
||||
t.Log("All ML-DSA functions work correctly in pure Go mode")
|
||||
}
|
||||
|
||||
func TestNoCGO_ThresholdReturnsError(t *testing.T) {
|
||||
_, err := NewThresholdContext(3, 5)
|
||||
if !errors.Is(err, ErrCGORequired) {
|
||||
t.Errorf("NewThresholdContext error = %v, want ErrCGORequired", err)
|
||||
func TestNoCGO_HashFunctions(t *testing.T) {
|
||||
data := []byte("test data for hashing")
|
||||
|
||||
// Test SHA3_256
|
||||
hash := SHA3_256(data)
|
||||
if len(hash) != 32 {
|
||||
t.Errorf("SHA3_256 output length = %d, want 32", len(hash))
|
||||
}
|
||||
t.Logf("SHA3-256: %x", hash)
|
||||
|
||||
// Test SHA3_512
|
||||
hash = SHA3_512(data)
|
||||
if len(hash) != 64 {
|
||||
t.Errorf("SHA3_512 output length = %d, want 64", len(hash))
|
||||
}
|
||||
t.Logf("SHA3-512: %x", hash[:32])
|
||||
|
||||
// Test BLAKE3
|
||||
hash = BLAKE3(data)
|
||||
if len(hash) != 32 {
|
||||
t.Errorf("BLAKE3 output length = %d, want 32", len(hash))
|
||||
}
|
||||
t.Logf("BLAKE3: %x", hash)
|
||||
|
||||
// Test BatchHash
|
||||
inputs := [][]byte{
|
||||
[]byte("message 1"),
|
||||
[]byte("message 2"),
|
||||
[]byte("message 3"),
|
||||
}
|
||||
hashes, err := BatchHash(inputs, HashTypeSHA3_256)
|
||||
if err != nil {
|
||||
t.Fatalf("BatchHash failed: %v", err)
|
||||
}
|
||||
if len(hashes) != len(inputs) {
|
||||
t.Errorf("BatchHash returned %d hashes, want %d", len(hashes), len(inputs))
|
||||
}
|
||||
|
||||
// Test methods on nil context (should not panic)
|
||||
var ctx *ThresholdContext
|
||||
ctx.Close() // Should not panic
|
||||
|
||||
_, _, err = ctx.Keygen(nil)
|
||||
if !errors.Is(err, ErrCGORequired) {
|
||||
t.Errorf("Keygen error = %v, want ErrCGORequired", err)
|
||||
}
|
||||
|
||||
t.Log("All threshold functions correctly return ErrCGORequired")
|
||||
t.Log("All hash functions work correctly in pure Go mode")
|
||||
}
|
||||
|
||||
func TestNoCGO_HashReturnsNil(t *testing.T) {
|
||||
data := []byte("test data")
|
||||
func TestNoCGO_ThresholdContext(t *testing.T) {
|
||||
// Test creating threshold context
|
||||
ctx, err := NewThresholdContext(2, 3) // 2-of-3
|
||||
if err != nil {
|
||||
t.Fatalf("NewThresholdContext failed: %v", err)
|
||||
}
|
||||
defer ctx.Close()
|
||||
|
||||
if hash := SHA3_256(data); hash != nil {
|
||||
t.Error("SHA3_256 should return nil without CGO")
|
||||
t.Logf("Threshold context created: t=%d, n=%d", ctx.t, ctx.n)
|
||||
|
||||
// Test keygen - returns (shares, pk, err)
|
||||
seed := make([]byte, 32)
|
||||
for i := range seed {
|
||||
seed[i] = byte(i + 42)
|
||||
}
|
||||
shares, pk, err := ctx.Keygen(seed)
|
||||
if err != nil {
|
||||
t.Fatalf("Threshold Keygen failed: %v", err)
|
||||
}
|
||||
if len(pk) == 0 {
|
||||
t.Fatal("Keygen returned empty public key")
|
||||
}
|
||||
if len(shares) != 3 {
|
||||
t.Errorf("Keygen returned %d shares, want 3", len(shares))
|
||||
}
|
||||
t.Logf("Threshold keygen complete: pk=%d bytes, %d shares", len(pk), len(shares))
|
||||
|
||||
// Test signing
|
||||
message := SHA3_256([]byte("threshold test message")) // 32 bytes for BLS
|
||||
partialSigs := make([][]byte, 2)
|
||||
indices := []uint32{0, 2} // Use first and third party
|
||||
|
||||
for i, idx := range indices {
|
||||
ps, err := ctx.PartialSign(idx, shares[idx], message)
|
||||
if err != nil {
|
||||
t.Fatalf("Threshold PartialSign for party %d failed: %v", idx, err)
|
||||
}
|
||||
partialSigs[i] = ps
|
||||
}
|
||||
t.Logf("Generated %d partial signatures", len(partialSigs))
|
||||
|
||||
// Test combination
|
||||
sig, err := ctx.Combine(partialSigs, indices)
|
||||
if err != nil {
|
||||
t.Fatalf("Threshold Combine failed: %v", err)
|
||||
}
|
||||
if len(sig) == 0 {
|
||||
t.Fatal("Combine returned empty signature")
|
||||
}
|
||||
t.Logf("Combined signature: %d bytes", len(sig))
|
||||
|
||||
// Test verification - Verify(sig, pk, msg)
|
||||
valid := ctx.Verify(sig, pk, message)
|
||||
if !valid {
|
||||
t.Error("Threshold Verify failed for valid signature")
|
||||
}
|
||||
|
||||
if hash := SHA3_512(data); hash != nil {
|
||||
t.Error("SHA3_512 should return nil without CGO")
|
||||
// Test verification with wrong message
|
||||
wrongMessage := SHA3_256([]byte("wrong message"))
|
||||
if ctx.Verify(sig, pk, wrongMessage) {
|
||||
t.Error("Threshold Verify should fail for wrong message")
|
||||
}
|
||||
|
||||
if hash := BLAKE3(data); hash != nil {
|
||||
t.Error("BLAKE3 should return nil without CGO")
|
||||
}
|
||||
|
||||
_, err := BatchHash([][]byte{data}, HashTypeSHA3_256)
|
||||
if !errors.Is(err, ErrCGORequired) {
|
||||
t.Errorf("BatchHash error = %v, want ErrCGORequired", err)
|
||||
}
|
||||
|
||||
t.Log("All hash functions correctly return nil/ErrCGORequired")
|
||||
t.Log("All threshold functions work correctly in pure Go mode")
|
||||
}
|
||||
|
||||
func TestNoCGO_ConsensusVerifyBlock(t *testing.T) {
|
||||
if ConsensusVerifyBlock(nil, nil, nil, nil, nil) {
|
||||
t.Error("ConsensusVerifyBlock should return false without CGO")
|
||||
}
|
||||
// Create test data - need arrays of signatures and public keys
|
||||
blockHash := SHA3_256([]byte("test block"))
|
||||
|
||||
t.Log("ConsensusVerifyBlock correctly returns false")
|
||||
// Generate BLS keypairs
|
||||
sk1, _ := BLSKeygen(nil)
|
||||
pk1, _ := BLSSecretKeyToPublicKey(sk1)
|
||||
sig1, _ := BLSSign(sk1, blockHash)
|
||||
|
||||
sk2, _ := BLSKeygen(nil)
|
||||
pk2, _ := BLSSecretKeyToPublicKey(sk2)
|
||||
sig2, _ := BLSSign(sk2, blockHash)
|
||||
|
||||
blsSigs := [][]byte{sig1, sig2}
|
||||
blsPKs := [][]byte{pk1, pk2}
|
||||
|
||||
// Test consensus verify with multiple BLS signatures
|
||||
result := ConsensusVerifyBlock(blsSigs, blsPKs, nil, nil, blockHash)
|
||||
if !result {
|
||||
t.Error("ConsensusVerifyBlock failed for valid signatures")
|
||||
}
|
||||
t.Logf("ConsensusVerifyBlock with %d BLS signatures: %v", len(blsSigs), result)
|
||||
|
||||
// Test with threshold signature too
|
||||
ctx, _ := NewThresholdContext(2, 3)
|
||||
defer ctx.Close()
|
||||
|
||||
shares, threshPK, _ := ctx.Keygen(nil)
|
||||
ps1, _ := ctx.PartialSign(0, shares[0], blockHash)
|
||||
ps2, _ := ctx.PartialSign(1, shares[1], blockHash)
|
||||
threshSig, _ := ctx.Combine([][]byte{ps1, ps2}, []uint32{0, 1})
|
||||
|
||||
result = ConsensusVerifyBlock(blsSigs, blsPKs, threshSig, threshPK, blockHash)
|
||||
if !result {
|
||||
t.Error("ConsensusVerifyBlock failed with threshold signature")
|
||||
}
|
||||
t.Logf("ConsensusVerifyBlock with BLS + threshold: %v", result)
|
||||
|
||||
t.Log("ConsensusVerifyBlock works correctly in pure Go mode")
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//go:build cgo
|
||||
//go:build cgo && gpu
|
||||
|
||||
// Package gpu provides GPU-accelerated cryptographic operations.
|
||||
package gpu
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//go:build cgo
|
||||
//go:build cgo && gpu
|
||||
|
||||
package gpu
|
||||
|
||||
|
||||
+325
-129
@@ -1,12 +1,17 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// BLS (Boneh-Lynn-Shacham) signature precompiled contracts
|
||||
// For aggregated signatures and threshold cryptography
|
||||
// Uses gnark-crypto for real BLS12-381 operations
|
||||
|
||||
package precompile
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
|
||||
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
|
||||
)
|
||||
|
||||
// BLS precompile addresses
|
||||
@@ -38,6 +43,10 @@ const (
|
||||
// Per-item costs for aggregation
|
||||
blsPerSignatureGas = 30000
|
||||
blsPerPublicKeyGas = 10000
|
||||
|
||||
// Point sizes
|
||||
g1Size = 48
|
||||
g2Size = 96
|
||||
)
|
||||
|
||||
// BLSVerify implements single BLS signature verification
|
||||
@@ -48,25 +57,47 @@ func (b *BLSVerify) RequiredGas(input []byte) uint64 {
|
||||
}
|
||||
|
||||
func (b *BLSVerify) Run(input []byte) ([]byte, error) {
|
||||
// Input: [96 bytes signature][48 bytes public key][message]
|
||||
if len(input) < 144 {
|
||||
// Input: [96 bytes signature (G2)][48 bytes public key (G1)][message]
|
||||
if len(input) < g2Size+g1Size+1 {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
// For now, this is a placeholder
|
||||
// Real implementation would use gnark-crypto BLS12-381
|
||||
// Parse signature (G2 point)
|
||||
var sig bls12381.G2Affine
|
||||
if _, err := sig.SetBytes(input[:g2Size]); err != nil {
|
||||
return nil, errors.New("invalid signature point")
|
||||
}
|
||||
|
||||
// Parse signature (G2 point, 96 bytes)
|
||||
signature := input[:96]
|
||||
|
||||
// Parse public key (G1 point, 48 bytes)
|
||||
pubKey := input[96:144]
|
||||
// Parse public key (G1 point)
|
||||
var pubKey bls12381.G1Affine
|
||||
if _, err := pubKey.SetBytes(input[g2Size : g2Size+g1Size]); err != nil {
|
||||
return nil, errors.New("invalid public key point")
|
||||
}
|
||||
|
||||
// Parse message
|
||||
message := input[144:]
|
||||
message := input[g2Size+g1Size:]
|
||||
|
||||
// Simplified verification (placeholder)
|
||||
valid := len(signature) == 96 && len(pubKey) == 48 && len(message) > 0
|
||||
// Hash message to G2
|
||||
msgPoint, err := bls12381.HashToG2(message, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("hash to curve failed")
|
||||
}
|
||||
|
||||
// Get G1 generator
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
|
||||
// Verify: e(pubKey, H(m)) == e(G1, sig)
|
||||
// Equivalent to: e(pubKey, H(m)) * e(-G1, sig) == 1
|
||||
var negG1 bls12381.G1Affine
|
||||
negG1.Neg(&g1Gen)
|
||||
|
||||
valid, err := bls12381.PairingCheck(
|
||||
[]bls12381.G1Affine{pubKey, negG1},
|
||||
[]bls12381.G2Affine{msgPoint, sig},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]byte, 32)
|
||||
if valid {
|
||||
@@ -76,11 +107,10 @@ func (b *BLSVerify) Run(input []byte) ([]byte, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// BLSAggregateVerify verifies multiple signatures with different messages
|
||||
// BLSAggregateVerify verifies aggregate signature with different messages
|
||||
type BLSAggregateVerify struct{}
|
||||
|
||||
func (b *BLSAggregateVerify) RequiredGas(input []byte) uint64 {
|
||||
// Parse number of signatures
|
||||
if len(input) < 1 {
|
||||
return blsAggregateVerifyGas
|
||||
}
|
||||
@@ -89,56 +119,79 @@ func (b *BLSAggregateVerify) RequiredGas(input []byte) uint64 {
|
||||
}
|
||||
|
||||
func (b *BLSAggregateVerify) Run(input []byte) ([]byte, error) {
|
||||
// Input: [1 byte num_sigs][signatures][public_keys][messages]
|
||||
if len(input) < 1 {
|
||||
// Input: [1 byte num_sigs][aggregate_sig (96)][public_keys][messages]
|
||||
if len(input) < 1+g2Size {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
numSigs := input[0]
|
||||
|
||||
// Calculate expected sizes
|
||||
sigSize := 96 * int(numSigs)
|
||||
pubKeySize := 48 * int(numSigs)
|
||||
|
||||
if len(input) < 1+sigSize+pubKeySize {
|
||||
return nil, errors.New("invalid input size")
|
||||
numSigs := int(input[0])
|
||||
if numSigs == 0 {
|
||||
return nil, errors.New("no signatures")
|
||||
}
|
||||
|
||||
// Parse signatures
|
||||
offset := 1
|
||||
signatures := make([][]byte, numSigs)
|
||||
for i := 0; i < int(numSigs); i++ {
|
||||
signatures[i] = input[offset : offset+96]
|
||||
offset += 96
|
||||
// Parse aggregate signature
|
||||
var aggSig bls12381.G2Affine
|
||||
if _, err := aggSig.SetBytes(input[1 : 1+g2Size]); err != nil {
|
||||
return nil, errors.New("invalid aggregate signature")
|
||||
}
|
||||
|
||||
// Parse public keys
|
||||
pubKeys := make([][]byte, numSigs)
|
||||
for i := 0; i < int(numSigs); i++ {
|
||||
pubKeys[i] = input[offset : offset+48]
|
||||
offset += 48
|
||||
offset := 1 + g2Size
|
||||
pubKeys := make([]bls12381.G1Affine, numSigs)
|
||||
for i := 0; i < numSigs; i++ {
|
||||
if offset+g1Size > len(input) {
|
||||
return nil, errors.New("invalid public key")
|
||||
}
|
||||
if _, err := pubKeys[i].SetBytes(input[offset : offset+g1Size]); err != nil {
|
||||
return nil, errors.New("invalid public key point")
|
||||
}
|
||||
offset += g1Size
|
||||
}
|
||||
|
||||
// Parse messages (variable length, encoded with length prefix)
|
||||
messages := make([][]byte, numSigs)
|
||||
for i := 0; i < int(numSigs); i++ {
|
||||
// Parse messages (length-prefixed)
|
||||
msgPoints := make([]bls12381.G2Affine, numSigs)
|
||||
for i := 0; i < numSigs; i++ {
|
||||
if offset+4 > len(input) {
|
||||
return nil, errors.New("invalid message encoding")
|
||||
}
|
||||
|
||||
msgLen := binary.BigEndian.Uint32(input[offset : offset+4])
|
||||
msgLen := int(binary.BigEndian.Uint32(input[offset : offset+4]))
|
||||
offset += 4
|
||||
|
||||
if offset+int(msgLen) > len(input) {
|
||||
if offset+msgLen > len(input) {
|
||||
return nil, errors.New("message too long")
|
||||
}
|
||||
|
||||
messages[i] = input[offset : offset+int(msgLen)]
|
||||
offset += int(msgLen)
|
||||
msg := input[offset : offset+msgLen]
|
||||
offset += msgLen
|
||||
|
||||
// Hash message to G2
|
||||
msgPoint, err := bls12381.HashToG2(msg, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("hash to curve failed")
|
||||
}
|
||||
msgPoints[i] = msgPoint
|
||||
}
|
||||
|
||||
// Verify aggregate signature (placeholder)
|
||||
valid := true // Would call actual BLS aggregate verify
|
||||
// Verify aggregate signature using multi-pairing
|
||||
// e(pk1, H(m1)) * e(pk2, H(m2)) * ... * e(pkn, H(mn)) == e(G1, aggSig)
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
|
||||
g1Points := make([]bls12381.G1Affine, numSigs+1)
|
||||
g2Points := make([]bls12381.G2Affine, numSigs+1)
|
||||
|
||||
copy(g1Points[:numSigs], pubKeys)
|
||||
copy(g2Points[:numSigs], msgPoints)
|
||||
|
||||
// Add negated generator paired with aggregate signature
|
||||
var negG1 bls12381.G1Affine
|
||||
negG1.Neg(&g1Gen)
|
||||
g1Points[numSigs] = negG1
|
||||
g2Points[numSigs] = aggSig
|
||||
|
||||
valid, err := bls12381.PairingCheck(g1Points, g2Points)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]byte, 32)
|
||||
if valid {
|
||||
@@ -161,31 +214,66 @@ func (b *BLSFastAggregate) RequiredGas(input []byte) uint64 {
|
||||
|
||||
func (b *BLSFastAggregate) Run(input []byte) ([]byte, error) {
|
||||
// Input: [1 byte num_keys][96 bytes aggregate_sig][public_keys][message]
|
||||
if len(input) < 98 {
|
||||
if len(input) < 1+g2Size {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
numKeys := input[0]
|
||||
numKeys := int(input[0])
|
||||
if numKeys == 0 {
|
||||
return nil, errors.New("no public keys")
|
||||
}
|
||||
|
||||
// Parse aggregate signature
|
||||
aggSig := input[1:97]
|
||||
var aggSig bls12381.G2Affine
|
||||
if _, err := aggSig.SetBytes(input[1 : 1+g2Size]); err != nil {
|
||||
return nil, errors.New("invalid aggregate signature")
|
||||
}
|
||||
|
||||
// Parse public keys
|
||||
offset := 97
|
||||
pubKeys := make([][]byte, numKeys)
|
||||
for i := 0; i < int(numKeys); i++ {
|
||||
if offset+48 > len(input) {
|
||||
// Parse and aggregate public keys
|
||||
offset := 1 + g2Size
|
||||
var aggPubKey bls12381.G1Affine
|
||||
|
||||
for i := 0; i < numKeys; i++ {
|
||||
if offset+g1Size > len(input) {
|
||||
return nil, errors.New("invalid public key")
|
||||
}
|
||||
pubKeys[i] = input[offset : offset+48]
|
||||
offset += 48
|
||||
var pk bls12381.G1Affine
|
||||
if _, err := pk.SetBytes(input[offset : offset+g1Size]); err != nil {
|
||||
return nil, errors.New("invalid public key point")
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
aggPubKey = pk
|
||||
} else {
|
||||
aggPubKey.Add(&aggPubKey, &pk)
|
||||
}
|
||||
offset += g1Size
|
||||
}
|
||||
|
||||
// Parse message (remainder)
|
||||
message := input[offset:]
|
||||
if len(message) == 0 {
|
||||
return nil, errors.New("empty message")
|
||||
}
|
||||
|
||||
// Fast aggregate verify (all sign same message)
|
||||
valid := len(aggSig) == 96 && len(message) > 0
|
||||
// Hash message to G2
|
||||
msgPoint, err := bls12381.HashToG2(message, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("hash to curve failed")
|
||||
}
|
||||
|
||||
// Verify: e(aggPubKey, H(m)) == e(G1, aggSig)
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
var negG1 bls12381.G1Affine
|
||||
negG1.Neg(&g1Gen)
|
||||
|
||||
valid, err := bls12381.PairingCheck(
|
||||
[]bls12381.G1Affine{aggPubKey, negG1},
|
||||
[]bls12381.G2Affine{msgPoint, aggSig},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]byte, 32)
|
||||
if valid {
|
||||
@@ -203,34 +291,72 @@ func (b *BLSThresholdVerify) RequiredGas(input []byte) uint64 {
|
||||
}
|
||||
|
||||
func (b *BLSThresholdVerify) Run(input []byte) ([]byte, error) {
|
||||
// Input: [1 byte threshold][1 byte num_shares][shares][message]
|
||||
if len(input) < 2 {
|
||||
// Input: [1 byte threshold][1 byte num_shares][48 bytes group_pubkey][shares with indices][message]
|
||||
if len(input) < 2+g1Size+g2Size {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
threshold := input[0]
|
||||
numShares := input[1]
|
||||
threshold := int(input[0])
|
||||
numShares := int(input[1])
|
||||
|
||||
if numShares < threshold {
|
||||
return nil, errors.New("insufficient shares for threshold")
|
||||
}
|
||||
|
||||
// Parse signature shares
|
||||
offset := 2
|
||||
shares := make([][]byte, numShares)
|
||||
for i := 0; i < int(numShares); i++ {
|
||||
if offset+96 > len(input) {
|
||||
// Parse group public key
|
||||
var groupPubKey bls12381.G1Affine
|
||||
if _, err := groupPubKey.SetBytes(input[2 : 2+g1Size]); err != nil {
|
||||
return nil, errors.New("invalid group public key")
|
||||
}
|
||||
|
||||
// Parse signature shares and their indices
|
||||
offset := 2 + g1Size
|
||||
shares := make([]bls12381.G2Affine, numShares)
|
||||
indices := make([]uint64, numShares)
|
||||
|
||||
for i := 0; i < numShares; i++ {
|
||||
if offset+8+g2Size > len(input) {
|
||||
return nil, errors.New("invalid share")
|
||||
}
|
||||
shares[i] = input[offset : offset+96]
|
||||
offset += 96
|
||||
|
||||
// Read index (8 bytes)
|
||||
indices[i] = binary.BigEndian.Uint64(input[offset : offset+8])
|
||||
offset += 8
|
||||
|
||||
// Read share (G2 point)
|
||||
if _, err := shares[i].SetBytes(input[offset : offset+g2Size]); err != nil {
|
||||
return nil, errors.New("invalid signature share")
|
||||
}
|
||||
offset += g2Size
|
||||
}
|
||||
|
||||
// Parse message
|
||||
message := input[offset:]
|
||||
if len(message) == 0 {
|
||||
return nil, errors.New("empty message")
|
||||
}
|
||||
|
||||
// Verify threshold signature (placeholder)
|
||||
valid := len(shares) >= int(threshold) && len(message) > 0
|
||||
// Combine shares using Lagrange interpolation
|
||||
combinedSig := combineThresholdShares(shares, indices, threshold)
|
||||
|
||||
// Hash message to G2
|
||||
msgPoint, err := bls12381.HashToG2(message, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("hash to curve failed")
|
||||
}
|
||||
|
||||
// Verify combined signature
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
var negG1 bls12381.G1Affine
|
||||
negG1.Neg(&g1Gen)
|
||||
|
||||
valid, err := bls12381.PairingCheck(
|
||||
[]bls12381.G1Affine{groupPubKey, negG1},
|
||||
[]bls12381.G2Affine{msgPoint, combinedSig},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]byte, 32)
|
||||
if valid {
|
||||
@@ -240,6 +366,108 @@ func (b *BLSThresholdVerify) Run(input []byte) ([]byte, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// combineThresholdShares combines threshold signature shares using Lagrange interpolation
|
||||
func combineThresholdShares(shares []bls12381.G2Affine, indices []uint64, threshold int) bls12381.G2Affine {
|
||||
var combined bls12381.G2Affine
|
||||
|
||||
for i := 0; i < threshold; i++ {
|
||||
// Compute Lagrange coefficient
|
||||
lambda := computeLagrangeCoeff(indices, i, threshold)
|
||||
|
||||
// Multiply share by lambda
|
||||
var scaledShare bls12381.G2Affine
|
||||
scaledShare.ScalarMultiplication(&shares[i], lambda.BigInt(new(big.Int)))
|
||||
|
||||
if i == 0 {
|
||||
combined = scaledShare
|
||||
} else {
|
||||
combined.Add(&combined, &scaledShare)
|
||||
}
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
|
||||
// computeLagrangeCoeff computes Lagrange coefficient for party at position i
|
||||
func computeLagrangeCoeff(indices []uint64, i, t int) fr.Element {
|
||||
var lambda fr.Element
|
||||
lambda.SetOne()
|
||||
|
||||
for j := 0; j < t; j++ {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
|
||||
// lambda *= (0 - x_j) / (x_i - x_j)
|
||||
// Since we're evaluating at 0: lambda *= -x_j / (x_i - x_j)
|
||||
var xi, xj, num, denom fr.Element
|
||||
xi.SetUint64(indices[i] + 1) // 1-indexed for proper interpolation
|
||||
xj.SetUint64(indices[j] + 1)
|
||||
|
||||
num.Neg(&xj) // -x_j
|
||||
denom.Sub(&xi, &xj) // x_i - x_j
|
||||
denom.Inverse(&denom)
|
||||
|
||||
num.Mul(&num, &denom)
|
||||
lambda.Mul(&lambda, &num)
|
||||
}
|
||||
|
||||
return lambda
|
||||
}
|
||||
|
||||
// BLSThresholdCombine combines threshold signature shares
|
||||
type BLSThresholdCombine struct{}
|
||||
|
||||
func (b *BLSThresholdCombine) RequiredGas(input []byte) uint64 {
|
||||
if len(input) < 1 {
|
||||
return blsThresholdCombineGas
|
||||
}
|
||||
numShares := uint64(input[0])
|
||||
return blsThresholdCombineGas + numShares*blsPerSignatureGas
|
||||
}
|
||||
|
||||
func (b *BLSThresholdCombine) Run(input []byte) ([]byte, error) {
|
||||
// Input: [1 byte num_shares][shares with indices...]
|
||||
if len(input) < 2 {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
numShares := int(input[0])
|
||||
threshold := int(input[1])
|
||||
|
||||
if numShares < threshold {
|
||||
return nil, errors.New("insufficient shares")
|
||||
}
|
||||
|
||||
// Parse shares and indices
|
||||
offset := 2
|
||||
shares := make([]bls12381.G2Affine, numShares)
|
||||
indices := make([]uint64, numShares)
|
||||
|
||||
for i := 0; i < numShares; i++ {
|
||||
if offset+8+g2Size > len(input) {
|
||||
return nil, errors.New("invalid share encoding")
|
||||
}
|
||||
|
||||
// Read index
|
||||
indices[i] = binary.BigEndian.Uint64(input[offset : offset+8])
|
||||
offset += 8
|
||||
|
||||
// Read share
|
||||
if _, err := shares[i].SetBytes(input[offset : offset+g2Size]); err != nil {
|
||||
return nil, errors.New("invalid signature share point")
|
||||
}
|
||||
offset += g2Size
|
||||
}
|
||||
|
||||
// Combine using Lagrange interpolation
|
||||
combined := combineThresholdShares(shares, indices, threshold)
|
||||
|
||||
// Return combined signature (96 bytes)
|
||||
bytes := combined.Bytes()
|
||||
return bytes[:], nil
|
||||
}
|
||||
|
||||
// BLSPublicKeyAggregate aggregates multiple BLS public keys
|
||||
type BLSPublicKeyAggregate struct{}
|
||||
|
||||
@@ -257,27 +485,36 @@ func (b *BLSPublicKeyAggregate) Run(input []byte) ([]byte, error) {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
numKeys := input[0]
|
||||
expectedSize := 1 + int(numKeys)*48
|
||||
numKeys := int(input[0])
|
||||
expectedSize := 1 + numKeys*g1Size
|
||||
|
||||
if len(input) != expectedSize {
|
||||
if len(input) < expectedSize {
|
||||
return nil, errors.New("invalid input size")
|
||||
}
|
||||
|
||||
// Aggregate public keys (placeholder)
|
||||
// Real implementation would add G1 points
|
||||
aggregatedKey := make([]byte, 48)
|
||||
if numKeys == 0 {
|
||||
return nil, errors.New("no public keys")
|
||||
}
|
||||
|
||||
// Simple XOR for placeholder (not cryptographically correct!)
|
||||
for i := 0; i < int(numKeys); i++ {
|
||||
offset := 1 + i*48
|
||||
pubKey := input[offset : offset+48]
|
||||
for j := 0; j < 48; j++ {
|
||||
aggregatedKey[j] ^= pubKey[j]
|
||||
// Parse and aggregate public keys using G1 point addition
|
||||
var aggregatedKey bls12381.G1Affine
|
||||
|
||||
for i := 0; i < numKeys; i++ {
|
||||
offset := 1 + i*g1Size
|
||||
var pk bls12381.G1Affine
|
||||
if _, err := pk.SetBytes(input[offset : offset+g1Size]); err != nil {
|
||||
return nil, errors.New("invalid public key point")
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
aggregatedKey = pk
|
||||
} else {
|
||||
aggregatedKey.Add(&aggregatedKey, &pk)
|
||||
}
|
||||
}
|
||||
|
||||
return aggregatedKey, nil
|
||||
bytes := aggregatedKey.Bytes()
|
||||
return bytes[:], nil
|
||||
}
|
||||
|
||||
// BLSHashToPoint hashes message to BLS12-381 G2 point
|
||||
@@ -293,16 +530,14 @@ func (b *BLSHashToPoint) Run(input []byte) ([]byte, error) {
|
||||
return nil, errors.New("empty input")
|
||||
}
|
||||
|
||||
// Hash to G2 point (placeholder)
|
||||
// Real implementation would use proper hash-to-curve
|
||||
g2Point := make([]byte, 96)
|
||||
|
||||
// Simple placeholder: use first 96 bytes of repeated hash
|
||||
for i := 0; i < 96; i++ {
|
||||
g2Point[i] = input[i%len(input)]
|
||||
// Use gnark-crypto's hash-to-curve implementation
|
||||
g2Point, err := bls12381.HashToG2(input, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("hash to curve failed")
|
||||
}
|
||||
|
||||
return g2Point, nil
|
||||
bytes := g2Point.Bytes()
|
||||
return bytes[:], nil
|
||||
}
|
||||
|
||||
// RegisterBLS registers all BLS precompiles
|
||||
@@ -316,45 +551,6 @@ func RegisterBLS(registry *Registry) {
|
||||
registry.Register(BLSHashToPointAddress, &BLSHashToPoint{})
|
||||
}
|
||||
|
||||
// BLSThresholdCombine combines threshold signature shares
|
||||
type BLSThresholdCombine struct{}
|
||||
|
||||
func (b *BLSThresholdCombine) RequiredGas(input []byte) uint64 {
|
||||
if len(input) < 1 {
|
||||
return blsThresholdCombineGas
|
||||
}
|
||||
numShares := uint64(input[0])
|
||||
return blsThresholdCombineGas + numShares*blsPerSignatureGas
|
||||
}
|
||||
|
||||
func (b *BLSThresholdCombine) Run(input []byte) ([]byte, error) {
|
||||
// Input: [1 byte num_shares][shares...]
|
||||
if len(input) < 1 {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
numShares := input[0]
|
||||
expectedSize := 1 + int(numShares)*96
|
||||
|
||||
if len(input) != expectedSize {
|
||||
return nil, errors.New("invalid input size")
|
||||
}
|
||||
|
||||
// Combine signature shares (placeholder)
|
||||
combinedSig := make([]byte, 96)
|
||||
|
||||
// Simple XOR for placeholder (not cryptographically correct!)
|
||||
for i := 0; i < int(numShares); i++ {
|
||||
offset := 1 + i*96
|
||||
share := input[offset : offset+96]
|
||||
for j := 0; j < 96; j++ {
|
||||
combinedSig[j] ^= share[j]
|
||||
}
|
||||
}
|
||||
|
||||
return combinedSig, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Auto-register BLS precompiles on package load
|
||||
RegisterBLS(PostQuantumRegistry)
|
||||
|
||||
@@ -8,8 +8,11 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
|
||||
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
|
||||
"github.com/luxfi/crypto/lamport"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -446,48 +449,111 @@ func TestBLSPrecompiles(t *testing.T) {
|
||||
t.Run("BLSVerify", func(t *testing.T) {
|
||||
verifier := &BLSVerify{}
|
||||
|
||||
// Create dummy input (placeholder implementation)
|
||||
// Format: [96 bytes sig][48 bytes pubkey][message]
|
||||
message := []byte("test message for BLS")
|
||||
// Generate valid BLS key pair and signature
|
||||
var scalar fr.Element
|
||||
scalar.SetRandom()
|
||||
|
||||
// Generate public key: pk = g1 * scalar
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
var pk bls12381.G1Affine
|
||||
pk.ScalarMultiplication(&g1Gen, scalar.BigInt(new(big.Int)))
|
||||
|
||||
// The precompile uses raw message with nil DST
|
||||
message := []byte("test message for BLS verification")
|
||||
msgPoint, err := bls12381.HashToG2(message, nil) // nil DST to match precompile
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sign: sig = H(m) * scalar
|
||||
var sig bls12381.G2Affine
|
||||
sig.ScalarMultiplication(&msgPoint, scalar.BigInt(new(big.Int)))
|
||||
|
||||
// Build input: [96 bytes sig (G2 compressed)][48 bytes pubkey (G1 compressed)][message]
|
||||
sigBytes := sig.Bytes()
|
||||
pkBytes := pk.Bytes()
|
||||
input := make([]byte, 96+48+len(message))
|
||||
rand.Read(input[:96]) // Random signature
|
||||
rand.Read(input[96:144]) // Random pubkey
|
||||
copy(input[:96], sigBytes[:])
|
||||
copy(input[96:144], pkBytes[:])
|
||||
copy(input[144:], message)
|
||||
|
||||
// Test gas
|
||||
gas := verifier.RequiredGas(input)
|
||||
assert.Equal(t, uint64(150000), gas)
|
||||
|
||||
// Test run (placeholder always returns success)
|
||||
// Test run - should verify successfully
|
||||
output, err := verifier.Run(input)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, output, 32)
|
||||
// Check that output indicates success (all zeros except last byte = 1)
|
||||
expected := make([]byte, 32)
|
||||
expected[31] = 1
|
||||
assert.Equal(t, expected, output)
|
||||
})
|
||||
|
||||
t.Run("BLSAggregateVerify", func(t *testing.T) {
|
||||
aggVerifier := &BLSAggregateVerify{}
|
||||
|
||||
// Build aggregate input
|
||||
// Format: [num_sigs(1)][signatures][pubkeys][encoded_messages]
|
||||
numSigs := 3
|
||||
sigSize := 96
|
||||
pubSize := 48
|
||||
sigSize := 96 // G2 compressed
|
||||
pubSize := 48 // G1 compressed
|
||||
msgSize := 32
|
||||
|
||||
totalSize := 1 + numSigs*(sigSize+pubSize+4+msgSize)
|
||||
// Generate valid BLS signatures
|
||||
var pubKeys []bls12381.G1Affine
|
||||
var sigs []bls12381.G2Affine
|
||||
var messages [][]byte
|
||||
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
|
||||
for i := 0; i < numSigs; i++ {
|
||||
var scalar fr.Element
|
||||
scalar.SetRandom()
|
||||
|
||||
// Generate public key
|
||||
var pk bls12381.G1Affine
|
||||
pk.ScalarMultiplication(&g1Gen, scalar.BigInt(new(big.Int)))
|
||||
pubKeys = append(pubKeys, pk)
|
||||
|
||||
// Create unique message
|
||||
msg := make([]byte, 32)
|
||||
msg[0] = byte(i)
|
||||
rand.Read(msg[1:])
|
||||
messages = append(messages, msg)
|
||||
|
||||
// Sign message with nil DST to match precompile
|
||||
msgPoint, _ := bls12381.HashToG2(msg, nil)
|
||||
var sig bls12381.G2Affine
|
||||
sig.ScalarMultiplication(&msgPoint, scalar.BigInt(new(big.Int)))
|
||||
sigs = append(sigs, sig)
|
||||
}
|
||||
|
||||
// Aggregate signatures: aggSig = sig[0] + sig[1] + ... + sig[n-1]
|
||||
var aggSig bls12381.G2Jac
|
||||
for i, sig := range sigs {
|
||||
if i == 0 {
|
||||
aggSig.FromAffine(&sig)
|
||||
} else {
|
||||
var sigJac bls12381.G2Jac
|
||||
sigJac.FromAffine(&sig)
|
||||
aggSig.AddAssign(&sigJac)
|
||||
}
|
||||
}
|
||||
var aggSigAffine bls12381.G2Affine
|
||||
aggSigAffine.FromJacobian(&aggSig)
|
||||
|
||||
// Build input: [num_sigs (1)][aggregate_sig (96)][public_keys (n*48)][messages with length prefix]
|
||||
totalSize := 1 + sigSize + numSigs*pubSize + numSigs*(4+msgSize)
|
||||
input := make([]byte, totalSize)
|
||||
input[0] = byte(numSigs)
|
||||
|
||||
offset := 1
|
||||
// Add signatures
|
||||
for i := 0; i < numSigs; i++ {
|
||||
rand.Read(input[offset : offset+sigSize])
|
||||
offset += sigSize
|
||||
}
|
||||
// Add aggregate signature
|
||||
aggSigBytes := aggSigAffine.Bytes()
|
||||
copy(input[1:1+sigSize], aggSigBytes[:])
|
||||
|
||||
offset := 1 + sigSize
|
||||
// Add public keys
|
||||
for i := 0; i < numSigs; i++ {
|
||||
rand.Read(input[offset : offset+pubSize])
|
||||
pkBytes := pubKeys[i].Bytes()
|
||||
copy(input[offset:offset+pubSize], pkBytes[:])
|
||||
offset += pubSize
|
||||
}
|
||||
|
||||
@@ -495,17 +561,16 @@ func TestBLSPrecompiles(t *testing.T) {
|
||||
for i := 0; i < numSigs; i++ {
|
||||
binary.BigEndian.PutUint32(input[offset:offset+4], uint32(msgSize))
|
||||
offset += 4
|
||||
rand.Read(input[offset : offset+msgSize])
|
||||
copy(input[offset:offset+msgSize], messages[i])
|
||||
offset += msgSize
|
||||
}
|
||||
|
||||
// Test gas
|
||||
gas := aggVerifier.RequiredGas(input)
|
||||
// blsAggregateVerifyGas = 200000, blsPerSignatureGas = 30000
|
||||
expectedGas := uint64(200000 + 30000*uint64(numSigs))
|
||||
assert.Equal(t, expectedGas, gas)
|
||||
|
||||
// Test run
|
||||
// Test run - should verify successfully
|
||||
output, err := aggVerifier.Run(input)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, output, 32)
|
||||
@@ -514,16 +579,28 @@ func TestBLSPrecompiles(t *testing.T) {
|
||||
t.Run("BLSPublicKeyAggregate", func(t *testing.T) {
|
||||
aggregator := &BLSPublicKeyAggregate{}
|
||||
|
||||
// Build input
|
||||
// Build input with valid BLS public keys
|
||||
// Format: [num_keys(1)][pubkeys...]
|
||||
numKeys := 5
|
||||
pubSize := 48
|
||||
pubSize := 48 // G1 compressed size
|
||||
|
||||
input := make([]byte, 1+numKeys*pubSize)
|
||||
input[0] = byte(numKeys)
|
||||
|
||||
// Generate valid BLS public keys
|
||||
for i := 0; i < numKeys; i++ {
|
||||
rand.Read(input[1+i*pubSize : 1+(i+1)*pubSize])
|
||||
// Generate random scalar
|
||||
var scalar fr.Element
|
||||
scalar.SetRandom()
|
||||
|
||||
// Compute public key: pk = g1 * scalar
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
var pk bls12381.G1Affine
|
||||
pk.ScalarMultiplication(&g1Gen, scalar.BigInt(new(big.Int)))
|
||||
|
||||
// Serialize to compressed format
|
||||
pkBytes := pk.Bytes()
|
||||
copy(input[1+i*pubSize:1+(i+1)*pubSize], pkBytes[:])
|
||||
}
|
||||
|
||||
// Test gas
|
||||
|
||||
Reference in New Issue
Block a user