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

Extract BLS library used in polybft to a standalone package #1981

Merged
merged 10 commits into from
Oct 31, 2023
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
"math/big"
"testing"

"github.com/0xPolygon/polygon-edge/helper/common"
"github.com/stretchr/testify/require"

"github.com/0xPolygon/polygon-edge/helper/common"
)

//go:embed testcases/*
Expand Down
3 changes: 1 addition & 2 deletions consensus/polybft/signer/private.go → bls/private.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import (
"math/big"
"strings"

bn256 "github.com/umbracle/go-eth-bn256"

"github.com/0xPolygon/polygon-edge/helper/hex"
bn256 "github.com/umbracle/go-eth-bn256"
)

// PrivateKey holds private key for bls implementation
Expand Down
File renamed without changes.
14 changes: 14 additions & 0 deletions consensus/polybft/signer/public.go → bls/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ package bls

import (
"encoding/base64"
"errors"
"fmt"
"math/big"

"github.com/0xPolygon/polygon-edge/helper/common"
bn256 "github.com/umbracle/go-eth-bn256"
)

const (
PublicKeySize = 128
)

var (
errInfinityPoint = errors.New("infinity point")
ErrInvalidPublicKeySize = fmt.Errorf("public key must be %d bytes long", PublicKeySize)
)

// PublicKey represents bls public key
type PublicKey struct {
g2 *bn256.G2
Expand Down Expand Up @@ -57,6 +67,10 @@ func (p *PublicKey) ToBigInt() [4]*big.Int {

// UnmarshalPublicKey unmarshals bytes to public key
func UnmarshalPublicKey(data []byte) (*PublicKey, error) {
if len(data) < PublicKeySize {
return nil, ErrInvalidPublicKeySize
}

g2 := new(bn256.G2)

if _, err := g2.Unmarshal(data); err != nil {
Expand Down
File renamed without changes.
13 changes: 10 additions & 3 deletions consensus/polybft/signer/signature.go → bls/signature.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package bls

import (
"errors"
"fmt"
"math/big"

bn256 "github.com/umbracle/go-eth-bn256"
)

const (
SignatureSize = 64
)

var (
ErrInvalidSignatureSize = fmt.Errorf("signature must be %d bytes long", SignatureSize)
)

// Signature represents bls signature which is point on the curve
type Signature struct {
g1 *bn256.G1
Expand Down Expand Up @@ -48,8 +55,8 @@ func (s Signature) ToBigInt() ([2]*big.Int, error) {

// UnmarshalSignature reads the signature from the given byte array
func UnmarshalSignature(raw []byte) (*Signature, error) {
if len(raw) == 0 {
return nil, errors.New("cannot unmarshal signature from empty slice")
if len(raw) < SignatureSize {
return nil, ErrInvalidSignatureSize
}

g1 := new(bn256.G1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"
"time"

"github.com/0xPolygon/polygon-edge/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
bn256 "github.com/umbracle/go-eth-bn256"
Expand All @@ -16,18 +17,23 @@ const (
participantsNumber = 64
)

var (
expectedDomain = crypto.Keccak256([]byte("ExpectedDomain"))
unexpectedDomain = crypto.Keccak256([]byte("UnexpectedDomain"))
)

func Test_VerifySignature(t *testing.T) {
t.Parallel()

validTestMsg, invalidTestMsg := testGenRandomBytes(t, messageSize), testGenRandomBytes(t, messageSize)

blsKey, _ := GenerateBlsKey()
signature, err := blsKey.Sign(validTestMsg, DomainValidatorSet)
signature, err := blsKey.Sign(validTestMsg, expectedDomain)
require.NoError(t, err)

assert.True(t, signature.Verify(blsKey.PublicKey(), validTestMsg, DomainValidatorSet))
assert.False(t, signature.Verify(blsKey.PublicKey(), invalidTestMsg, DomainValidatorSet))
assert.False(t, signature.Verify(blsKey.PublicKey(), validTestMsg, DomainCheckpointManager))
assert.True(t, signature.Verify(blsKey.PublicKey(), validTestMsg, expectedDomain))
assert.False(t, signature.Verify(blsKey.PublicKey(), invalidTestMsg, expectedDomain))
assert.False(t, signature.Verify(blsKey.PublicKey(), validTestMsg, unexpectedDomain))
}

func Test_VerifySignature_NegativeCases(t *testing.T) {
Expand All @@ -42,10 +48,10 @@ func Test_VerifySignature_NegativeCases(t *testing.T) {
blsKey, err := GenerateBlsKey()
require.NoError(t, err)

signature, err := blsKey.Sign(validTestMsg, DomainValidatorSet)
signature, err := blsKey.Sign(validTestMsg, expectedDomain)
require.NoError(t, err)

require.True(t, signature.Verify(blsKey.PublicKey(), validTestMsg, DomainValidatorSet))
require.True(t, signature.Verify(blsKey.PublicKey(), validTestMsg, expectedDomain))

rawSig, err := signature.Marshal()
require.NoError(t, err)
Expand All @@ -62,11 +68,11 @@ func Test_VerifySignature_NegativeCases(t *testing.T) {

publicKey := blsKey.PublicKey()
publicKey.g2.Add(publicKey.g2, randomG2) // change public key g2 point
require.False(t, sigTemp.Verify(publicKey, validTestMsg, DomainValidatorSet))
require.False(t, sigTemp.Verify(publicKey, validTestMsg, expectedDomain))

publicKey = blsKey.PublicKey()
publicKey.g2.ScalarMult(publicKey.g2, x) // change public key g2 point
require.False(t, sigTemp.Verify(publicKey, validTestMsg, DomainValidatorSet))
require.False(t, sigTemp.Verify(publicKey, validTestMsg, expectedDomain))
}
})

Expand All @@ -83,7 +89,7 @@ func Test_VerifySignature_NegativeCases(t *testing.T) {
b := msgCopy[i]
msgCopy[i] = b + 1

require.False(t, sigTemp.Verify(blsKey.PublicKey(), msgCopy, DomainValidatorSet))
require.False(t, sigTemp.Verify(blsKey.PublicKey(), msgCopy, expectedDomain))
msgCopy[i] = b
}
})
Expand All @@ -99,13 +105,13 @@ func Test_VerifySignature_NegativeCases(t *testing.T) {
require.NoError(t, err)

sigCopy.g1.Add(sigCopy.g1, randomG1) // change signature
require.False(t, sigCopy.Verify(blsKey.PublicKey(), validTestMsg, DomainValidatorSet))
require.False(t, sigCopy.Verify(blsKey.PublicKey(), validTestMsg, expectedDomain))

sigCopy, err = UnmarshalSignature(rawSig)
require.NoError(t, err)

sigCopy.g1.ScalarMult(sigCopy.g1, x) // change signature
require.False(t, sigCopy.Verify(blsKey.PublicKey(), validTestMsg, DomainValidatorSet))
require.False(t, sigCopy.Verify(blsKey.PublicKey(), validTestMsg, expectedDomain))
}
})
}
Expand All @@ -119,19 +125,19 @@ func Test_AggregatedSignatureSimple(t *testing.T) {
bls2, _ := GenerateBlsKey()
bls3, _ := GenerateBlsKey()

sig1, err := bls1.Sign(validTestMsg, DomainValidatorSet)
sig1, err := bls1.Sign(validTestMsg, expectedDomain)
require.NoError(t, err)
sig2, err := bls2.Sign(validTestMsg, DomainValidatorSet)
sig2, err := bls2.Sign(validTestMsg, expectedDomain)
require.NoError(t, err)
sig3, err := bls3.Sign(validTestMsg, DomainValidatorSet)
sig3, err := bls3.Sign(validTestMsg, expectedDomain)
require.NoError(t, err)

signatures := Signatures{sig1, sig2, sig3}
publicKeys := PublicKeys{bls1.PublicKey(), bls2.PublicKey(), bls3.PublicKey()}

assert.True(t, signatures.Aggregate().Verify(publicKeys.Aggregate(), validTestMsg, DomainValidatorSet))
assert.False(t, signatures.Aggregate().Verify(publicKeys.Aggregate(), invalidTestMsg, DomainValidatorSet))
assert.False(t, signatures.Aggregate().Verify(publicKeys.Aggregate(), validTestMsg, DomainCheckpointManager))
assert.True(t, signatures.Aggregate().Verify(publicKeys.Aggregate(), validTestMsg, expectedDomain))
assert.False(t, signatures.Aggregate().Verify(publicKeys.Aggregate(), invalidTestMsg, expectedDomain))
assert.False(t, signatures.Aggregate().Verify(publicKeys.Aggregate(), validTestMsg, unexpectedDomain))
}

func Test_AggregatedSignature(t *testing.T) {
Expand All @@ -154,7 +160,7 @@ func Test_AggregatedSignature(t *testing.T) {
)

for _, key := range blsKeys {
signature, err := key.Sign(validTestMsg, DomainValidatorSet)
signature, err := key.Sign(validTestMsg, expectedDomain)
require.NoError(t, err)

signatures = append(signatures, signature)
Expand All @@ -164,10 +170,10 @@ func Test_AggregatedSignature(t *testing.T) {
aggSignature := signatures.Aggregate()
aggPubs := publicKeys.Aggregate()

assert.True(t, aggSignature.Verify(aggPubs, validTestMsg, DomainValidatorSet))
assert.False(t, aggSignature.Verify(aggPubs, invalidTestMsg, DomainValidatorSet))
assert.True(t, aggSignature.VerifyAggregated([]*PublicKey(publicKeys), validTestMsg, DomainValidatorSet))
assert.False(t, aggSignature.VerifyAggregated([]*PublicKey(publicKeys), invalidTestMsg, DomainValidatorSet))
assert.True(t, aggSignature.Verify(aggPubs, validTestMsg, expectedDomain))
assert.False(t, aggSignature.Verify(aggPubs, invalidTestMsg, expectedDomain))
assert.True(t, aggSignature.VerifyAggregated([]*PublicKey(publicKeys), validTestMsg, expectedDomain))
assert.False(t, aggSignature.VerifyAggregated([]*PublicKey(publicKeys), invalidTestMsg, expectedDomain))
}

func TestSignature_BigInt(t *testing.T) {
Expand All @@ -178,7 +184,7 @@ func TestSignature_BigInt(t *testing.T) {
bls1, err := GenerateBlsKey()
require.NoError(t, err)

sig1, err := bls1.Sign(validTestMsg, DomainCheckpointManager)
sig1, err := bls1.Sign(validTestMsg, unexpectedDomain)
assert.NoError(t, err)

_, err = sig1.ToBigInt()
Expand All @@ -193,7 +199,7 @@ func TestSignature_Unmarshal(t *testing.T) {
bls1, err := GenerateBlsKey()
require.NoError(t, err)

sig, err := bls1.Sign(validTestMsg, DomainCheckpointManager)
sig, err := bls1.Sign(validTestMsg, unexpectedDomain)
require.NoError(t, err)

bytes, err := sig.Marshal()
Expand Down
42 changes: 24 additions & 18 deletions consensus/polybft/signer/common.go → bls/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,46 @@ package bls
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"log"
"math/big"

pcrypto "github.com/0xPolygon/polygon-edge/crypto"
bn256 "github.com/umbracle/go-eth-bn256"
)

const (
DomainValidatorSetString = "DOMAIN_CHILD_VALIDATOR_SET"
DomainCheckpointManagerString = "DOMAIN_CHECKPOINT_MANAGER"
DomainCommonSigningString = "DOMAIN_COMMON_SIGNING"
DomainStateReceiverString = "DOMAIN_STATE_RECEIVER"
)

var errInfinityPoint = fmt.Errorf("infinity point")

var (
// negated g2 point
negG2Point = mustG2Point("198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d") //nolint

// g2 point
g2Point = mustG2Point("198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") //nolint
)

// GenerateBlsKey creates a random private key (and its corresponding public key)
func GenerateBlsKey() (*PrivateKey, error) {
s, err := randomK(rand.Reader)
if err != nil {
return nil, err
}

// domain used to map hash to G1 used by (child) validator set
DomainValidatorSet = pcrypto.Keccak256([]byte(DomainValidatorSetString))
return &PrivateKey{s: s}, nil
}

// domain used to map hash to G1 used by child checkpoint manager
DomainCheckpointManager = pcrypto.Keccak256([]byte(DomainCheckpointManagerString))
// CreateRandomBlsKeys creates an array of random private and their corresponding public keys
func CreateRandomBlsKeys(total int) ([]*PrivateKey, error) {
blsKeys := make([]*PrivateKey, total)

DomainCommonSigning = pcrypto.Keccak256([]byte(DomainCommonSigningString))
DomainStateReceiver = pcrypto.Keccak256([]byte(DomainStateReceiverString))
)
for i := 0; i < total; i++ {
blsKey, err := GenerateBlsKey()
if err != nil {
return nil, err
}

blsKeys[i] = blsKey
}

return blsKeys, nil
}

func mustG2Point(str string) *bn256.G2 {
buf, err := hex.DecodeString(str)
Expand Down
66 changes: 66 additions & 0 deletions bls/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package bls

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_SingleSign(t *testing.T) {
t.Parallel()

validTestMsg, invalidTestMsg := testGenRandomBytes(t, messageSize), testGenRandomBytes(t, messageSize)

blsKey, err := GenerateBlsKey() // structure which holds private/public key pair
require.NoError(t, err)

// Sign valid message
signature, err := blsKey.Sign(validTestMsg, expectedDomain)
require.NoError(t, err)

isOk := signature.Verify(blsKey.PublicKey(), validTestMsg, expectedDomain)
assert.True(t, isOk)

// Verify if invalid message is signed with correct private key. Only use public key for the verification
// this should fail => isOk = false
isOk = signature.Verify(blsKey.PublicKey(), invalidTestMsg, unexpectedDomain)
assert.False(t, isOk)
}

func Test_AggregatedSign(t *testing.T) {
t.Parallel()

validTestMsg, invalidTestMsg := testGenRandomBytes(t, messageSize), testGenRandomBytes(t, messageSize)

keys, err := CreateRandomBlsKeys(participantsNumber) // create keys for validators
require.NoError(t, err)

pubKeys := make([]*PublicKey, len(keys))

for i, key := range keys {
pubKeys[i] = key.PublicKey()
}

signatures := Signatures{}

// test all signatures at once
for i := 0; i < len(keys); i++ {
sign, err := keys[i].Sign(validTestMsg, expectedDomain)
require.NoError(t, err)

signatures = append(signatures, sign)

// verify correctness of AggregateSignature
aggSig := signatures.Aggregate()

isOk := aggSig.VerifyAggregated(pubKeys[:i+1], validTestMsg, expectedDomain)
assert.True(t, isOk)

isOk = aggSig.VerifyAggregated(pubKeys[:i+1], invalidTestMsg, expectedDomain)
assert.False(t, isOk)

isOk = aggSig.VerifyAggregated(pubKeys[:i+1], validTestMsg, unexpectedDomain)
assert.False(t, isOk)
}
}
2 changes: 1 addition & 1 deletion command/polybftsecrets/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"path"
"testing"

bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer"
"github.com/0xPolygon/polygon-edge/bls"
"github.com/0xPolygon/polygon-edge/secrets/helper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down
Loading
Loading