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
88 changes: 88 additions & 0 deletions crypto/keys/bls12381/multisig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package bls12381

import (
"encoding/base64"
"fmt"
"os"

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

func aggregatePublicKey(pks []*PubKey) *blst.P1Affine {
pubkeys := make([]*blst.P1Affine, len(pks))
for i, pk := range pks {
pubkeys[i] = new(blst.P1Affine).Deserialize(pk.Key)
if pubkeys[i] == nil {
panic("Failed to deserialize public key")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
panic("Failed to deserialize public key")
return errors.New("failed to deserialize public key")

}
}

aggregator := new(blst.P1Aggregate)
b := aggregator.Aggregate(pubkeys, false)
if !b {
panic("Failed to aggregate public keys")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
panic("Failed to aggregate public keys")
return errors.New("failed to aggregate public keys")

}
apk := aggregator.ToAffine()

return apk
}

// AggregateSignature combines a set of verified signatures into a single bls signature
func AggregateSignature(sigs [][]byte) []byte {
sigmas := make([]*blst.P2Affine, len(sigs))
for i, sig := range sigs {
sigmas[i] = new(blst.P2Affine).Uncompress(sig)
if sigmas[i] == nil {
panic("Failed to deserialize signature")
}
}

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

// VerifyMultiSignature assumes public key is already validated
func VerifyMultiSignature(msg []byte, sig []byte, pks []*PubKey) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually go VerifyXXX / ValidateXXX methods return an error instead of a bool, which is nil when it's ok and provide some insight why it didn't validate when it's not. This would also have the advantage to allow removing all those panics by replacing them with errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, thanks, will change. I was just following what they did for secp256k1 and ed25519.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I've already seen sdk dev loves to panic all around the place. Without any comments on those like // panic here is fine because XYZ it's hard to tell how safe it is. Up to you to change it, I don't have the whole context in mind!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made changes according to your comments and recommitted. Could you please have a look? Thanks

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) bool {
// messages must be pairwise distinct
if !Unique(msgs) {
fmt.Fprintf(os.Stdout, "messages must be pairwise distinct")
return false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fmt.Fprintf(os.Stdout, "messages must be pairwise distinct")
return false
return errors.New("messages must be pairwise distinct")

}

apks := make([]*blst.P1Affine, len(pkss))
for i, pks := range pkss {
apks[i] = aggregatePublicKey(pks)
}

sigma := new(blst.P2Affine).Uncompress(sig)
if sigma == nil {
panic("Failed to deserialize signature")
}

dst := []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_")
return sigma.AggregateVerify(true, apks, false, msgs, dst)
}
118 changes: 118 additions & 0 deletions crypto/keys/bls12381/multisig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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 := bls.AggregateSignature(sigs)
assert.True(t, bls.VerifyMultiSignature(msg, aggSig, pks))

}

func TestBlsAggSig(t *testing.T) {
total := 5
//sks := make([]*bls.PrivKey, total)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove if not needed

Suggested change
//sks := make([]*bls.PrivKey, total)

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 := bls.AggregateSignature(sigs)
assert.True(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 := bls.AggregateSignature(sigs)

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 := bls.AggregateSignature(sigs)
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint64 sequence = 4;
bool pop_is_valid = 5;
uint64 sequence = 4;
bool pop_is_valid = 5;

}

// ModuleAccount defines an account for modules that holds coins on a pool.
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 {
panic(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return error instead of panic

}
}
}

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