Skip to content

Commit

Permalink
Bls (#105)
Browse files Browse the repository at this point in the history
* bls signature for basic account

* benchmark for bls and ed25519

* added bls sig verify cost to genesis

* Revert "Merge branch 'fetchai:master' into bls"

This reverts commit a5dd8ea, reversing
changes made to 082e071.

* format using go tools

* nuisance golangci-lint errors

* POP interfaces in accounts and authentication

* add bls multsig operations

* fixed golangci-lint error

* changes after comments
  • Loading branch information
kitounliu authored and daeMOn63 committed Nov 12, 2021
1 parent 69515d4 commit 8856c68
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 47 deletions.
2 changes: 1 addition & 1 deletion crypto/keys/bls12381/bls12381.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func GenPrivKey() *PrivKey {
return &PrivKey{Key: genPrivKey(crypto.CReader())}
}

// genPrivKey generates a new secp256k1 private key using the provided reader.
// genPrivKey generates a new bls12381 private key using the provided reader.
func genPrivKey(rand io.Reader) []byte {
var ikm [SeedSize]byte
_, err := io.ReadFull(rand, ikm[:])
Expand Down
102 changes: 102 additions & 0 deletions crypto/keys/bls12381/multisig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package bls12381

import (
"encoding/base64"
"fmt"

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

func aggregatePublicKey(pks []*PubKey) (*blst.P1Affine, error) {
pubkeys := make([]*blst.P1Affine, len(pks))
for i, pk := range pks {
pubkeys[i] = new(blst.P1Affine).Deserialize(pk.Key)
if pubkeys[i] == nil {
return nil, fmt.Errorf("failed to deserialize public key")
}
}

aggregator := new(blst.P1Aggregate)
b := aggregator.Aggregate(pubkeys, false)
if !b {
return nil, fmt.Errorf("failed to aggregate public keys")
}
apk := aggregator.ToAffine()

return apk, nil
}

// AggregateSignature combines a set of verified signatures into a single bls signature
func AggregateSignature(sigs [][]byte) ([]byte, error) {
sigmas := make([]*blst.P2Affine, len(sigs))
for i, sig := range sigs {
sigmas[i] = new(blst.P2Affine).Uncompress(sig)
if sigmas[i] == nil {
return nil, fmt.Errorf("failed to deserialize the %d-th signature", i)
}
}

aggregator := new(blst.P2Aggregate)
b := aggregator.Aggregate(sigmas, false)
if !b {
return nil, fmt.Errorf("failed to aggregate signatures")
}
aggSigBytes := aggregator.ToAffine().Compress()
return aggSigBytes, nil
}

// VerifyMultiSignature assumes public key is already validated
func VerifyMultiSignature(msg []byte, sig []byte, pks []*PubKey) error {
return VerifyAggregateSignature([][]byte{msg}, sig, [][]*PubKey{pks})
}

func Unique(msgs [][]byte) bool {
if len(msgs) <= 1 {
return true
}
msgMap := make(map[string]bool, len(msgs))
for _, msg := range msgs {
s := base64.StdEncoding.EncodeToString(msg)
if _, ok := msgMap[s]; ok {
return false
}
msgMap[s] = true
}
return true
}

func VerifyAggregateSignature(msgs [][]byte, sig []byte, pkss [][]*PubKey) error {
n := len(msgs)
if n == 0 {
return fmt.Errorf("messages cannot be empty")
}

if len(pkss) != n {
return fmt.Errorf("the number of messages and public key sets must match")
}

if !Unique(msgs) {
return fmt.Errorf("messages must be pairwise distinct")
}

apks := make([]*blst.P1Affine, len(pkss))
for i, pks := range pkss {
apk, err := aggregatePublicKey(pks)
if err != nil {
return fmt.Errorf("cannot aggregate public keys: %s", err.Error())
}
apks[i] = apk
}

sigma := new(blst.P2Affine).Uncompress(sig)
if sigma == nil {
return fmt.Errorf("failed to deserialize signature")
}

dst := []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_")
if !sigma.AggregateVerify(true, apks, false, msgs, dst) {
return fmt.Errorf("failed to verify signature")
}

return nil
}
124 changes: 124 additions & 0 deletions crypto/keys/bls12381/multisig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package bls12381_test

import (
"fmt"
"testing"

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

bls "github.com/cosmos/cosmos-sdk/crypto/keys/bls12381"
)

func TestBlsMultiSig(t *testing.T) {
total := 5
pks := make([]*bls.PubKey, total)
sigs := make([][]byte, total)
msg := []byte("hello world")
for i := 0; i < total; i++ {
sk := bls.GenPrivKey()
pk, ok := sk.PubKey().(*bls.PubKey)
require.True(t, ok)

sig, err := sk.Sign(msg)
require.Nil(t, err)

pks[i] = pk
sigs[i] = sig
}

aggSig, err := bls.AggregateSignature(sigs)
require.Nil(t, err)

assert.Nil(t, bls.VerifyMultiSignature(msg, aggSig, pks))

}

func TestBlsAggSig(t *testing.T) {
total := 5
pks := make([][]*bls.PubKey, total)
sigs := make([][]byte, total)
msgs := make([][]byte, total)
for i := 0; i < total; i++ {
msgs[i] = []byte(fmt.Sprintf("message %d", i))
sk := bls.GenPrivKey()
pk, ok := sk.PubKey().(*bls.PubKey)
require.True(t, ok)

sig, err := sk.Sign(msgs[i])
require.Nil(t, err)

pks[i] = []*bls.PubKey{pk}
sigs[i] = sig
}

aggSig, err := bls.AggregateSignature(sigs)
require.Nil(t, err)

assert.Nil(t, bls.VerifyAggregateSignature(msgs, aggSig, pks))

}

func benchmarkBlsVerifyMulti(total int, b *testing.B) {
pks := make([]*bls.PubKey, total)
sigs := make([][]byte, total)
msg := []byte("hello world")
for i := 0; i < total; i++ {
sk := bls.GenPrivKey()
pk, ok := sk.PubKey().(*bls.PubKey)
require.True(b, ok)

sig, err := sk.Sign(msg)
require.Nil(b, err)

pks[i] = pk
sigs[i] = sig
}

aggSig, err := bls.AggregateSignature(sigs)
require.Nil(b, err)

b.ResetTimer()
for i := 0; i < b.N; i++ {
bls.VerifyMultiSignature(msg, aggSig, pks)
}
}

func BenchmarkBlsVerifyMulti8(b *testing.B) { benchmarkBlsVerifyMulti(8, b) }
func BenchmarkBlsVerifyMulti16(b *testing.B) { benchmarkBlsVerifyMulti(16, b) }
func BenchmarkBlsVerifyMulti32(b *testing.B) { benchmarkBlsVerifyMulti(32, b) }
func BenchmarkBlsVerifyMulti64(b *testing.B) { benchmarkBlsVerifyMulti(64, b) }
func BenchmarkBlsVerifyMulti128(b *testing.B) { benchmarkBlsVerifyMulti(128, b) }

func benchmarkBlsVerifyAgg(total int, b *testing.B) {
pks := make([][]*bls.PubKey, total)
sigs := make([][]byte, total)
msgs := make([][]byte, total)
for i := 0; i < total; i++ {
msgs[i] = []byte(fmt.Sprintf("message %d", i))
sk := bls.GenPrivKey()
pk, ok := sk.PubKey().(*bls.PubKey)
require.True(b, ok)

sig, err := sk.Sign(msgs[i])
require.Nil(b, err)

pks[i] = []*bls.PubKey{pk}
sigs[i] = sig
}

aggSig, err := bls.AggregateSignature(sigs)
require.Nil(b, err)

b.ResetTimer()

for i := 0; i < b.N; i++ {
bls.VerifyAggregateSignature(msgs, aggSig, pks)
}
}

func BenchmarkBlsVerifyAgg8(b *testing.B) { benchmarkBlsVerifyAgg(8, b) }
func BenchmarkBlsVerifyAgg16(b *testing.B) { benchmarkBlsVerifyAgg(16, b) }
func BenchmarkBlsVerifyAgg32(b *testing.B) { benchmarkBlsVerifyAgg(32, b) }
func BenchmarkBlsVerifyAgg64(b *testing.B) { benchmarkBlsVerifyAgg(64, b) }
func BenchmarkBlsVerifyAgg128(b *testing.B) { benchmarkBlsVerifyAgg(128, b) }
1 change: 1 addition & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,7 @@ type for additional functionality (e.g. vesting).
| `pub_key` | [google.protobuf.Any](#google.protobuf.Any) | | |
| `account_number` | [uint64](#uint64) | | |
| `sequence` | [uint64](#uint64) | | |
| `pop_is_valid` | [bool](#bool) | | |



Expand Down
1 change: 1 addition & 0 deletions proto/cosmos/auth/v1beta1/auth.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ message BaseAccount {
[(gogoproto.jsontag) = "public_key,omitempty", (gogoproto.moretags) = "yaml:\"public_key\""];
uint64 account_number = 3 [(gogoproto.moretags) = "yaml:\"account_number\""];
uint64 sequence = 4;
bool pop_is_valid = 5;
}

// ModuleAccount defines an account for modules that holds coins on a pool.
Expand Down
3 changes: 3 additions & 0 deletions types/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ var (
// supported.
ErrNotSupported = Register(RootCodespace, 37, "feature not supported")

// ErrInvalidPop to doc
ErrInvalidPop = Register(RootCodespace, 38, "invalid pop for public key")

// ErrPanic is only set when we recover from a panic, so we know to
// redact potentially sensitive system info
ErrPanic = Register(UndefinedCodespace, 111222, "panic")
Expand Down
1 change: 1 addition & 0 deletions x/auth/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func NewAnteHandler(
NewDeductFeeDecorator(ak, bankKeeper),
NewSigGasConsumeDecorator(ak, sigGasConsumer),
NewSigVerificationDecorator(ak, signModeHandler),
NewSetPopValidDecorator(ak),
NewIncrementSequenceDecorator(ak),
)
}
34 changes: 34 additions & 0 deletions x/auth/ante/sigverify.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,40 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
return next(ctx, tx, simulate)
}

// SetPopValidDecorator handles the validation status of the proof-of-possession (POP) of an individual public key.
// A valid transaction and signature can be viewed as a POP for the signer's public key.
// POP is required when forming a compact multisig group in order to prevent rogue public key attacks.
type SetPopValidDecorator struct {
ak AccountKeeper
}

func NewSetPopValidDecorator(ak AccountKeeper) SetPopValidDecorator {
return SetPopValidDecorator{
ak: ak,
}
}

func (spvd SetPopValidDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
}

for _, addr := range sigTx.GetSigners() {
acc := spvd.ak.GetAccount(ctx, addr)
pk := acc.GetPubKey()

switch pk.(type) {
case *bls12381.PubKey, *secp256k1.PubKey, *ed25519.PubKey:
if err := acc.SetPopValid(true); err != nil {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPop, err.Error())
}
}
}

return next(ctx, tx, simulate)
}

// IncrementSequenceDecorator handles incrementing sequences of all signers.
// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note,
// there is no need to execute IncrementSequenceDecorator on RecheckTX since
Expand Down
23 changes: 23 additions & 0 deletions x/auth/types/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func NewBaseAccount(address sdk.AccAddress, pubKey cryptotypes.PubKey, accountNu
Address: address.String(),
AccountNumber: accountNumber,
Sequence: sequence,
PopIsValid: false,
}

err := acc.SetPubKey(pubKey)
Expand Down Expand Up @@ -117,6 +118,20 @@ func (acc *BaseAccount) SetSequence(seq uint64) error {
return nil
}

// SetPopValid - Implements sdk.AccountI.
func (acc *BaseAccount) SetPopValid(isValid bool) error {
if acc.PubKey == nil {
return errors.New("public key is not set yet")
}
acc.PopIsValid = isValid
return nil
}

// GetPopValid - Implements sdk.AccountI.
func (acc *BaseAccount) GetPopValid() bool {
return acc.PopIsValid
}

// Validate checks for errors on the account fields
func (acc BaseAccount) Validate() error {
if acc.Address == "" || acc.PubKey == nil {
Expand Down Expand Up @@ -222,6 +237,11 @@ func (ma ModuleAccount) SetSequence(seq uint64) error {
return fmt.Errorf("not supported for module accounts")
}

// SetPopValid - Implements AccountI
func (ma ModuleAccount) SetPopValid(isValid bool) error {
return fmt.Errorf("not supported for module accounts")
}

// Validate checks for errors on the account fields
func (ma ModuleAccount) Validate() error {
if strings.TrimSpace(ma.Name) == "" {
Expand Down Expand Up @@ -324,6 +344,9 @@ type AccountI interface {
GetSequence() uint64
SetSequence(uint64) error

GetPopValid() bool
SetPopValid(bool) error

// Ensure that account implements stringer
String() string
}
Expand Down
Loading

0 comments on commit 8856c68

Please sign in to comment.