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

Bls #105

Merged
merged 14 commits into from
Jul 26, 2021
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 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 @@ -289,6 +289,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