Files
crypto/precompile/bls.go
T

362 lines
9.0 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// BLS (Boneh-Lynn-Shacham) signature precompiled contracts
// For aggregated signatures and threshold cryptography
package precompile
import (
"encoding/binary"
"errors"
)
// BLS precompile addresses
var (
// BLS12-381 operations
BLSVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000160")
BLSAggregateVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000161")
BLSFastAggregateAddress = HexToAddress("0x0000000000000000000000000000000000000162")
// Threshold BLS operations
BLSThresholdVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000163")
BLSThresholdCombineAddress = HexToAddress("0x0000000000000000000000000000000000000164")
// BLS key operations
BLSPublicKeyAggregateAddress = HexToAddress("0x0000000000000000000000000000000000000165")
BLSHashToPointAddress = HexToAddress("0x0000000000000000000000000000000000000166")
)
// Gas costs for BLS operations
const (
blsVerifyGas = 150000
blsAggregateVerifyGas = 200000
blsFastAggregateGas = 100000
blsThresholdVerifyGas = 250000
blsThresholdCombineGas = 180000
blsPublicKeyAggregateGas = 50000
blsHashToPointGas = 80000
// Per-item costs for aggregation
blsPerSignatureGas = 30000
blsPerPublicKeyGas = 10000
)
// BLSVerify implements single BLS signature verification
type BLSVerify struct{}
func (b *BLSVerify) RequiredGas(input []byte) uint64 {
return blsVerifyGas
}
func (b *BLSVerify) Run(input []byte) ([]byte, error) {
// Input: [96 bytes signature][48 bytes public key][message]
if len(input) < 144 {
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, 96 bytes)
signature := input[:96]
// Parse public key (G1 point, 48 bytes)
pubKey := input[96:144]
// Parse message
message := input[144:]
// Simplified verification (placeholder)
valid := len(signature) == 96 && len(pubKey) == 48 && len(message) > 0
result := make([]byte, 32)
if valid {
result[31] = 0x01
}
return result, nil
}
// BLSAggregateVerify verifies multiple signatures with different messages
type BLSAggregateVerify struct{}
func (b *BLSAggregateVerify) RequiredGas(input []byte) uint64 {
// Parse number of signatures
if len(input) < 1 {
return blsAggregateVerifyGas
}
numSigs := uint64(input[0])
return blsAggregateVerifyGas + numSigs*blsPerSignatureGas
}
func (b *BLSAggregateVerify) Run(input []byte) ([]byte, error) {
// Input: [1 byte num_sigs][signatures][public_keys][messages]
if len(input) < 1 {
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")
}
// Parse signatures
offset := 1
signatures := make([][]byte, numSigs)
for i := 0; i < int(numSigs); i++ {
signatures[i] = input[offset : offset+96]
offset += 96
}
// Parse public keys
pubKeys := make([][]byte, numSigs)
for i := 0; i < int(numSigs); i++ {
pubKeys[i] = input[offset : offset+48]
offset += 48
}
// Parse messages (variable length, encoded with length prefix)
messages := make([][]byte, numSigs)
for i := 0; i < int(numSigs); i++ {
if offset+4 > len(input) {
return nil, errors.New("invalid message encoding")
}
msgLen := binary.BigEndian.Uint32(input[offset : offset+4])
offset += 4
if offset+int(msgLen) > len(input) {
return nil, errors.New("message too long")
}
messages[i] = input[offset : offset+int(msgLen)]
offset += int(msgLen)
}
// Verify aggregate signature (placeholder)
valid := true // Would call actual BLS aggregate verify
result := make([]byte, 32)
if valid {
result[31] = 0x01
}
return result, nil
}
// BLSFastAggregate verifies aggregate signature with same message
type BLSFastAggregate struct{}
func (b *BLSFastAggregate) RequiredGas(input []byte) uint64 {
if len(input) < 1 {
return blsFastAggregateGas
}
numKeys := uint64(input[0])
return blsFastAggregateGas + numKeys*blsPerPublicKeyGas
}
func (b *BLSFastAggregate) Run(input []byte) ([]byte, error) {
// Input: [1 byte num_keys][96 bytes aggregate_sig][public_keys][message]
if len(input) < 98 {
return nil, errors.New("input too short")
}
numKeys := input[0]
// Parse aggregate signature
aggSig := input[1:97]
// Parse public keys
offset := 97
pubKeys := make([][]byte, numKeys)
for i := 0; i < int(numKeys); i++ {
if offset+48 > len(input) {
return nil, errors.New("invalid public key")
}
pubKeys[i] = input[offset : offset+48]
offset += 48
}
// Parse message (remainder)
message := input[offset:]
// Fast aggregate verify (all sign same message)
valid := len(aggSig) == 96 && len(message) > 0
result := make([]byte, 32)
if valid {
result[31] = 0x01
}
return result, nil
}
// BLSThresholdVerify verifies threshold signature
type BLSThresholdVerify struct{}
func (b *BLSThresholdVerify) RequiredGas(input []byte) uint64 {
return blsThresholdVerifyGas
}
func (b *BLSThresholdVerify) Run(input []byte) ([]byte, error) {
// Input: [1 byte threshold][1 byte num_shares][shares][message]
if len(input) < 2 {
return nil, errors.New("input too short")
}
threshold := input[0]
numShares := 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) {
return nil, errors.New("invalid share")
}
shares[i] = input[offset : offset+96]
offset += 96
}
// Parse message
message := input[offset:]
// Verify threshold signature (placeholder)
valid := len(shares) >= int(threshold) && len(message) > 0
result := make([]byte, 32)
if valid {
result[31] = 0x01
}
return result, nil
}
// BLSPublicKeyAggregate aggregates multiple BLS public keys
type BLSPublicKeyAggregate struct{}
func (b *BLSPublicKeyAggregate) RequiredGas(input []byte) uint64 {
if len(input) < 1 {
return blsPublicKeyAggregateGas
}
numKeys := uint64(input[0])
return blsPublicKeyAggregateGas + numKeys*blsPerPublicKeyGas
}
func (b *BLSPublicKeyAggregate) Run(input []byte) ([]byte, error) {
// Input: [1 byte num_keys][public_keys...]
if len(input) < 1 {
return nil, errors.New("input too short")
}
numKeys := input[0]
expectedSize := 1 + int(numKeys)*48
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)
// 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]
}
}
return aggregatedKey, nil
}
// BLSHashToPoint hashes message to BLS12-381 G2 point
type BLSHashToPoint struct{}
func (b *BLSHashToPoint) RequiredGas(input []byte) uint64 {
return blsHashToPointGas
}
func (b *BLSHashToPoint) Run(input []byte) ([]byte, error) {
// Input: message to hash
if len(input) == 0 {
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)]
}
return g2Point, nil
}
// RegisterBLS registers all BLS precompiles
func RegisterBLS(registry *Registry) {
registry.Register(BLSVerifyAddress, &BLSVerify{})
registry.Register(BLSAggregateVerifyAddress, &BLSAggregateVerify{})
registry.Register(BLSFastAggregateAddress, &BLSFastAggregate{})
registry.Register(BLSThresholdVerifyAddress, &BLSThresholdVerify{})
registry.Register(BLSThresholdCombineAddress, &BLSThresholdCombine{})
registry.Register(BLSPublicKeyAggregateAddress, &BLSPublicKeyAggregate{})
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)
}