Skip to content

Commit

Permalink
Extract BLS library used in polybft to a standalone package (#1981)
Browse files Browse the repository at this point in the history
* Introduce bls package

* Remove MarshalMessageToBigInt (unused)

* Use bls package in polybft consensus

* Adopt changes from #1973

* Remove package alias for bls package

* Revert GeneratePrivateKey rename

* Revive Test_MakeKOSKSignature and Test_AggregatedSign

* Rebase fix

* Fix TestAccount

* Reorder imports
  • Loading branch information
Stefan-Ethernal authored Oct 31, 2023
1 parent 96a5d55 commit 34e08f1
Show file tree
Hide file tree
Showing 45 changed files with 310 additions and 276 deletions.
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
File renamed without changes.
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 @@ -9,25 +9,32 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
bn256 "github.com/umbracle/go-eth-bn256"

"github.com/0xPolygon/polygon-edge/crypto"
)

const (
messageSize = 5000
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 +49,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 +69,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 +90,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 +106,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 +126,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 +161,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 +171,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 +185,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 +200,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
File renamed without changes.
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)
}
}
5 changes: 3 additions & 2 deletions command/polybftsecrets/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (
"path"
"testing"

bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer"
"github.com/0xPolygon/polygon-edge/secrets/helper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/umbracle/ethgo/wallet"

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

// Test initKeys
Expand Down
Loading

0 comments on commit 34e08f1

Please sign in to comment.