Skip to content

Commit

Permalink
feat: Implement high level function for bls_12381 (axieinfinity#296)
Browse files Browse the repository at this point in the history
  • Loading branch information
DNK90 authored and minh-bq committed Sep 12, 2023
1 parent 403caab commit 6264ae0
Show file tree
Hide file tree
Showing 30 changed files with 2,997 additions and 130 deletions.
21 changes: 21 additions & 0 deletions common/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ func LeftPadBytes(slice []byte, l int) []byte {
return padded
}

// PadTo pads a byte slice to the given size. If the byte slice is larger than the given size, the
// original slice is returned.
func PadTo(b []byte, size int) []byte {
if len(b) >= size {
return b
}
return append(b, make([]byte, size-len(b))...)
}

// TrimLeftZeroes returns a subslice of s without leading zeroes
func TrimLeftZeroes(s []byte) []byte {
idx := 0
Expand All @@ -137,3 +146,15 @@ func TrimRightZeroes(s []byte) []byte {
}
return s[:idx]
}

// Copy2dBytes will copy and return a non-nil 2d byte slice, otherwise it returns nil.
func Copy2dBytes(ary [][]byte) [][]byte {
if ary != nil {
copied := make([][]byte, len(ary))
for i, a := range ary {
copied[i] = CopyBytes(a)
}
return copied
}
return nil
}
48 changes: 48 additions & 0 deletions common/bytes_go120.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//go:build go1.20
// +build go1.20

package common

// These methods use go1.20 syntax to convert a byte slice to a fixed size array.

// ToBytes4 is a convenience method for converting a byte slice to a fix
// sized 4 byte array. This method will truncate the input if it is larger
// than 4 bytes.
func ToBytes4(x []byte) [4]byte {
return [4]byte(PadTo(x, 4))
}

// ToBytes20 is a convenience method for converting a byte slice to a fix
// sized 20 byte array. This method will truncate the input if it is larger
// than 20 bytes.
func ToBytes20(x []byte) [20]byte {
return [20]byte(PadTo(x, 20))
}

// ToBytes32 is a convenience method for converting a byte slice to a fix
// sized 32 byte array. This method will truncate the input if it is larger
// than 32 bytes.
func ToBytes32(x []byte) [32]byte {
return [32]byte(PadTo(x, 32))
}

// ToBytes48 is a convenience method for converting a byte slice to a fix
// sized 48 byte array. This method will truncate the input if it is larger
// than 48 bytes.
func ToBytes48(x []byte) [48]byte {
return [48]byte(PadTo(x, 48))
}

// ToBytes64 is a convenience method for converting a byte slice to a fix
// sized 64 byte array. This method will truncate the input if it is larger
// than 64 bytes.
func ToBytes64(x []byte) [64]byte {
return [64]byte(PadTo(x, 64))
}

// ToBytes96 is a convenience method for converting a byte slice to a fix
// sized 96 byte array. This method will truncate the input if it is larger
// than 96 bytes.
func ToBytes96(x []byte) [96]byte {
return [96]byte(PadTo(x, 96))
}
75 changes: 75 additions & 0 deletions crypto/bls/bls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Package bls implements a go-wrapper around a library implementing the
// BLS12-381 curve and signature scheme. This package exposes a public API for
// verifying and aggregating BLS signatures used by Ethereum.
package bls

import (
"github.com/ethereum/go-ethereum/crypto/bls/blst"
"github.com/ethereum/go-ethereum/crypto/bls/common"
"github.com/ethereum/go-ethereum/crypto/bls/herumi"
)

// Initialize herumi temporarily while we transition to blst for ethdo.
func init() {
herumi.HerumiInit()
}

// SecretKeyFromBytes creates a BLS private key from a BigEndian byte slice.
func SecretKeyFromBytes(privKey []byte) (SecretKey, error) {
return blst.SecretKeyFromBytes(privKey)
}

// PublicKeyFromBytes creates a BLS public key from a BigEndian byte slice.
func PublicKeyFromBytes(pubKey []byte) (PublicKey, error) {
return blst.PublicKeyFromBytes(pubKey)
}

// SignatureFromBytes creates a BLS signature from a LittleEndian byte slice.
func SignatureFromBytes(sig []byte) (Signature, error) {
return blst.SignatureFromBytes(sig)
}

// MultipleSignaturesFromBytes creates a slice of BLS signatures from a LittleEndian 2d-byte slice.
func MultipleSignaturesFromBytes(sigs [][]byte) ([]Signature, error) {
return blst.MultipleSignaturesFromBytes(sigs)
}

// AggregatePublicKeys aggregates the provided raw public keys into a single key.
func AggregatePublicKeys(pubs [][]byte) (PublicKey, error) {
return blst.AggregatePublicKeys(pubs)
}

// AggregateMultiplePubkeys aggregates the provided decompressed keys into a single key.
func AggregateMultiplePubkeys(pubs []PublicKey) PublicKey {
return blst.AggregateMultiplePubkeys(pubs)
}

// AggregateSignatures converts a list of signatures into a single, aggregated sig.
func AggregateSignatures(sigs []common.Signature) common.Signature {
return blst.AggregateSignatures(sigs)
}

// AggregateCompressedSignatures converts a list of compressed signatures into a single, aggregated sig.
func AggregateCompressedSignatures(multiSigs [][]byte) (common.Signature, error) {
return blst.AggregateCompressedSignatures(multiSigs)
}

// VerifySignature verifies a single signature. For performance reason, always use VerifyMultipleSignatures if possible.
func VerifySignature(sig []byte, msg [32]byte, pubKey common.PublicKey) (bool, error) {
return blst.VerifySignature(sig, msg, pubKey)
}

// VerifyMultipleSignatures verifies multiple signatures for distinct messages securely.
func VerifyMultipleSignatures(sigs [][]byte, msgs [][32]byte, pubKeys []common.PublicKey) (bool, error) {
return blst.VerifyMultipleSignatures(sigs, msgs, pubKeys)
}

// NewAggregateSignature creates a blank aggregate signature.
func NewAggregateSignature() common.Signature {
return blst.NewAggregateSignature()
}

// RandKey creates a new private key using a random input.
func RandKey() (common.SecretKey, error) {
return blst.RandKey()
}
30 changes: 30 additions & 0 deletions crypto/bls/bls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package bls

import (
"github.com/stretchr/testify/require"
"testing"

"github.com/ethereum/go-ethereum/crypto/bls/common"
)

func TestDisallowZeroSecretKeys(t *testing.T) {
t.Run("blst", func(t *testing.T) {
// Blst does a zero check on the key during deserialization.
_, err := SecretKeyFromBytes(common.ZeroSecretKey[:])
require.Equal(t, common.ErrSecretUnmarshal, err)
})
}

func TestDisallowZeroPublicKeys(t *testing.T) {
t.Run("blst", func(t *testing.T) {
_, err := PublicKeyFromBytes(common.InfinitePublicKey[:])
require.Equal(t, common.ErrInfinitePubKey, err)
})
}

func TestDisallowZeroPublicKeys_AggregatePubkeys(t *testing.T) {
t.Run("blst", func(t *testing.T) {
_, err := AggregatePublicKeys([][]byte{common.InfinitePublicKey[:], common.InfinitePublicKey[:]})
require.Equal(t, common.ErrInfinitePubKey, err)
})
}
11 changes: 11 additions & 0 deletions crypto/bls/blst/aliases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled

package blst

import blst "github.com/supranational/blst/bindings/go"

// Internal types for blst.
type blstPublicKey = blst.P1Affine
type blstSignature = blst.P2Affine
type blstAggregateSignature = blst.P2Aggregate
type blstAggregatePublicKey = blst.P1Aggregate
64 changes: 64 additions & 0 deletions crypto/bls/blst/bls_benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled

package blst_test

import (
"github.com/stretchr/testify/require"
"testing"

"github.com/ethereum/go-ethereum/crypto/bls/blst"
"github.com/ethereum/go-ethereum/crypto/bls/common"
)

func BenchmarkSignature_Verify(b *testing.B) {
sk, err := blst.RandKey()
require.NoError(b, err)

msg := []byte("Some msg")
sig := sk.Sign(msg)

b.ResetTimer()
for i := 0; i < b.N; i++ {
if !sig.Verify(sk.PublicKey(), msg) {
b.Fatal("could not verify sig")
}
}
}

func BenchmarkSignature_AggregateVerify(b *testing.B) {
sigN := 128 // MAX_ATTESTATIONS per block.

var pks []common.PublicKey
var sigs []common.Signature
var msgs [][32]byte
for i := 0; i < sigN; i++ {
msg := [32]byte{'s', 'i', 'g', 'n', 'e', 'd', byte(i)}
sk, err := blst.RandKey()
require.NoError(b, err)
sig := sk.Sign(msg[:])
pks = append(pks, sk.PublicKey())
sigs = append(sigs, sig)
msgs = append(msgs, msg)
}
aggregated := blst.AggregateSignatures(sigs)

b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if !aggregated.AggregateVerify(pks, msgs) {
b.Fatal("could not verify aggregate sig")
}
}
}

func BenchmarkSecretKey_Marshal(b *testing.B) {
key, err := blst.RandKey()
require.NoError(b, err)
d := key.Marshal()

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := blst.SecretKeyFromBytes(d)
_ = err
}
}
6 changes: 6 additions & 0 deletions crypto/bls/blst/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Package blst implements a go-wrapper around a library implementing the
// BLS12-381 curve and signature scheme. This package exposes a public API for
// verifying and aggregating BLS signatures used by Ethereum.
//
// This implementation uses the library written by Supranational, blst.
package blst
18 changes: 18 additions & 0 deletions crypto/bls/blst/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//go:build ((linux && amd64) || (linux && arm64) || (darwin && amd64) || (darwin && arm64) || (windows && amd64)) && !blst_disabled

package blst

import (
"runtime"

blst "github.com/supranational/blst/bindings/go"
)

func init() {
// Reserve 1 core for general application work
maxProcs := runtime.GOMAXPROCS(0) - 1
if maxProcs <= 0 {
maxProcs = 1
}
blst.SetMaxProcs(maxProcs)
}
Loading

0 comments on commit 6264ae0

Please sign in to comment.