Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/high level function for bls12381 #296

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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