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

multisig checks and optimization #8600

Merged
merged 23 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
27e77b6
Add Equal method to the CompactBitArray
robert-zaremba Feb 16, 2021
ea4e8d2
optimize NumTrueBitsBefore
robert-zaremba Feb 16, 2021
ddc0f44
fix compact_bit_array
robert-zaremba Feb 16, 2021
c67d8fa
add more tests and update comments
robert-zaremba Feb 16, 2021
bae3332
add check for unique keys
robert-zaremba Feb 16, 2021
18a7484
add unit tests
robert-zaremba Feb 16, 2021
4452122
LegacyAminoPubKey: rollback pubkey uniqueness check
robert-zaremba Feb 17, 2021
1a085ad
comment update
robert-zaremba Feb 17, 2021
7c7475d
Bechmark NumTrueBitsBefore
robert-zaremba Feb 17, 2021
116917e
Merge branch 'master' into robert/ms-check
robert-zaremba Feb 17, 2021
fd44602
Adding one more test
robert-zaremba Feb 17, 2021
af6ad96
changelog update
robert-zaremba Feb 17, 2021
90ec49b
Update crypto/types/compact_bit_array.go
robert-zaremba Feb 17, 2021
5e05091
change iff to if and only if
robert-zaremba Feb 17, 2021
128e92d
Merge branch 'master' into robert/ms-check
Feb 18, 2021
9541f7b
Merge branch 'master' into robert/ms-check
robert-zaremba Feb 19, 2021
2931f79
add comment to NumTrueBitsBefore
robert-zaremba Feb 22, 2021
8b21377
Merge remote-tracking branch 'origin/master' into robert/ms-check
robert-zaremba Feb 22, 2021
e719db2
merge conflict
robert-zaremba Feb 22, 2021
3bd5c2f
Merge branch 'master' into robert/ms-check
Feb 22, 2021
2272eaa
Merge branch 'master' into robert/ms-check
Feb 23, 2021
0a381ed
Changelog cosmetic update
robert-zaremba Feb 23, 2021
4b7ca4d
Update CHANGELOG.md
Feb 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* (crypto/types) [\#8600](https://github.com/cosmos/cosmos-sdk/pull/8600) `CompactBitArray`: optimizing the `NumTrueBitsBefore` method and adding `Equal` method.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

### Bug Fixes

Expand Down Expand Up @@ -83,7 +84,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### State Machine Breaking

* (x/ibc) [\#8266](https://github.com/cosmos/cosmos-sdk/issues/8266) Add amino JSON support for IBC MsgTransfer in order to support Ledger text signing transfer transactions.
* (x/ibc) [\#8404](https://github.com/cosmos/cosmos-sdk/pull/8404) Reorder IBC `ChanOpenAck` and `ChanOpenConfirm` handler execution to perform core handler first, followed by application callbacks.
* (x/ibc) [\#8404](https://github.com/cosmos/cosmos-sdk/pull/8404) Reorder IBC `ChanOpenAck` and `ChanOpenConfirm` handler execution to perform core handler first, followed by application callbacks.
alessio marked this conversation as resolved.
Show resolved Hide resolved
alessio marked this conversation as resolved.
Show resolved Hide resolved

### Bug Fixes

Expand Down
12 changes: 9 additions & 3 deletions crypto/keys/multisig/multisig.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ var _ multisigtypes.PubKey = &LegacyAminoPubKey{}
var _ types.UnpackInterfacesMessage = &LegacyAminoPubKey{}

// NewLegacyAminoPubKey returns a new LegacyAminoPubKey.
// Multisig can be constructed with multiple same keys - it will increase the power of
// the owner of that key (he will still need to add multiple signatures in the right order).
alessio marked this conversation as resolved.
Show resolved Hide resolved
// Panics if len(pubKeys) < k or 0 >= k.
func NewLegacyAminoPubKey(k int, pubKeys []cryptotypes.PubKey) *LegacyAminoPubKey {
if k <= 0 {
Expand All @@ -40,23 +42,27 @@ func (m *LegacyAminoPubKey) Bytes() []byte {
return AminoCdc.MustMarshalBinaryBare(m)
}

// VerifyMultisignature implements the multisigtypes.PubKey VerifyMultisignature method
// VerifyMultisignature implements the multisigtypes.PubKey VerifyMultisignature method.
// The signatures must be added in an order corresponding to the public keys order in
// LegacyAminoPubKey. It's OK to have multiple same keys in the multisig - it will increase
alessio marked this conversation as resolved.
Show resolved Hide resolved
// the power of the owner of that key - in that case the signer will still need to append
// multiple same signatures in the right order.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
func (m *LegacyAminoPubKey) VerifyMultisignature(getSignBytes multisigtypes.GetSignBytesFunc, sig *signing.MultiSignatureData) error {
bitarray := sig.BitArray
sigs := sig.Signatures
size := bitarray.Count()
pubKeys := m.GetPubKeys()
// ensure bit array is the correct size
if len(pubKeys) != size {
return fmt.Errorf("bit array size is incorrect %d", len(pubKeys))
return fmt.Errorf("bit array size is incorrect, expecting: %d", len(pubKeys))
}
// ensure size of signature list
if len(sigs) < int(m.Threshold) || len(sigs) > size {
return fmt.Errorf("signature size is incorrect %d", len(sigs))
}
// ensure at least k signatures are set
if bitarray.NumTrueBitsBefore(size) < int(m.Threshold) {
return fmt.Errorf("minimum number of signatures not set, have %d, expected %d", bitarray.NumTrueBitsBefore(size), int(m.Threshold))
return fmt.Errorf("not enough signatures set, have %d, expected %d", bitarray.NumTrueBitsBefore(size), int(m.Threshold))
}
// index in the list of signatures which we are concerned with.
sigIndex := 0
Expand Down
64 changes: 43 additions & 21 deletions crypto/keys/multisig/multisig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
)

func TestNewMultiSig(t *testing.T) {
require := require.New(t)
pk1 := secp256k1.GenPrivKey().PubKey()
pks := []cryptotypes.PubKey{pk1, pk1}

require.NotNil(kmultisig.NewLegacyAminoPubKey(1, pks),
"Should support not unique public keys")
}

func TestAddress(t *testing.T) {
msg := []byte{1, 2, 3, 4}
pubKeys, _ := generatePubKeysAndSignatures(5, msg)
Expand Down Expand Up @@ -85,21 +94,20 @@ func TestVerifyMultisignature(t *testing.T) {

testCases := []struct {
msg string
malleate func()
malleate func(*require.Assertions)
expectPass bool
}{
{
"nested multisignature",
func() {
func(require *require.Assertions) {
genPk, genSig := generateNestedMultiSignature(3, msg)
sig = genSig
pk = genPk
},
true,
},
{
}, {
"wrong size for sig bit array",
func() {
func(require *require.Assertions) {
pubKeys, _ := generatePubKeysAndSignatures(3, msg)
pk = kmultisig.NewLegacyAminoPubKey(3, pubKeys)
sig = multisig.NewMultisig(1)
Expand All @@ -108,7 +116,7 @@ func TestVerifyMultisignature(t *testing.T) {
},
{
"single signature data, expects the first k signatures to be valid",
func() {
func(require *require.Assertions) {
k := 2
signingIndices := []int{0, 3, 1}
pubKeys, sigs := generatePubKeysAndSignatures(5, msg)
Expand All @@ -119,32 +127,26 @@ func TestVerifyMultisignature(t *testing.T) {
for i := 0; i < k-1; i++ {
signingIndex := signingIndices[i]
require.NoError(
t,
multisig.AddSignatureFromPubKey(sig, sigs[signingIndex], pubKeys[signingIndex], pubKeys),
)
require.Error(
t,
pk.VerifyMultisignature(signBytesFn, sig),
"multisig passed when i < k, i %d", i,
)
require.NoError(
t,
multisig.AddSignatureFromPubKey(sig, sigs[signingIndex], pubKeys[signingIndex], pubKeys),
)
require.Equal(
t,
i+1,
len(sig.Signatures),
"adding a signature for the same pubkey twice increased signature count by 2, index %d", i,
)
}
require.Error(
t,
pk.VerifyMultisignature(signBytesFn, sig),
"multisig passed with k - 1 sigs",
)
require.NoError(
t,
multisig.AddSignatureFromPubKey(
sig,
sigs[signingIndices[k]],
Expand All @@ -153,30 +155,50 @@ func TestVerifyMultisignature(t *testing.T) {
),
)
require.NoError(
t,
pk.VerifyMultisignature(signBytesFn, sig),
"multisig failed after k good signatures",
)
},
true,
},
{
}, {
"duplicate signatures",
func() {
func(require *require.Assertions) {
pubKeys, sigs := generatePubKeysAndSignatures(5, msg)
pk = kmultisig.NewLegacyAminoPubKey(2, pubKeys)
sig = multisig.NewMultisig(5)

require.Error(t, pk.VerifyMultisignature(signBytesFn, sig))
require.Error(pk.VerifyMultisignature(signBytesFn, sig))
multisig.AddSignatureFromPubKey(sig, sigs[0], pubKeys[0], pubKeys)
// Add second signature manually
sig.Signatures = append(sig.Signatures, sigs[0])
},
false,
},
{
}, {
"duplicated key",
func(require *require.Assertions) {
// here we test an edge case where we create a multi sig with two same
// keys. It should work.
pubkeys, sigs := generatePubKeysAndSignatures(3, msg)
pubkeys[1] = pubkeys[0]
pk = kmultisig.NewLegacyAminoPubKey(2, pubkeys)
sig = multisig.NewMultisig(len(pubkeys))
multisig.AddSignature(sig, sigs[0], 0)
multisig.AddSignature(sig, sigs[0], 1)
},
true,
}, {
"same key used twice",
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
func(require *require.Assertions) {
pubkeys, sigs := generatePubKeysAndSignatures(3, msg)
pk = kmultisig.NewLegacyAminoPubKey(2, pubkeys)
sig = multisig.NewMultisig(len(pubkeys))
multisig.AddSignature(sig, sigs[0], 0)
multisig.AddSignature(sig, sigs[0], 1)
},
false,
}, {
"unable to verify signature",
func() {
func(require *require.Assertions) {
pubKeys, _ := generatePubKeysAndSignatures(2, msg)
_, sigs := generatePubKeysAndSignatures(2, msg)
pk = kmultisig.NewLegacyAminoPubKey(2, pubKeys)
Expand All @@ -190,7 +212,7 @@ func TestVerifyMultisignature(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.msg, func(t *testing.T) {
tc.malleate()
tc.malleate(require.New(t))
err := pk.VerifyMultisignature(signBytesFn, sig)
if tc.expectPass {
require.NoError(t, err)
Expand Down
45 changes: 33 additions & 12 deletions crypto/types/compact_bit_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ func NewCompactBitArray(bits int) *CompactBitArray {
func (bA *CompactBitArray) Count() int {
if bA == nil {
return 0
} else if bA.ExtraBitsStored == uint32(0) {
} else if bA.ExtraBitsStored == 0 {
return len(bA.Elems) * 8
}

return (len(bA.Elems)-1)*8 + int(bA.ExtraBitsStored)
}

// GetIndex returns the bit at index i within the bit array.
// GetIndex returns true if a bit at index i is set and false otherwise.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
// The behavior is undefined if i >= bA.Count()
func (bA *CompactBitArray) GetIndex(i int) bool {
if bA == nil {
Expand All @@ -47,11 +47,11 @@ func (bA *CompactBitArray) GetIndex(i int) bool {
return false
}

return bA.Elems[i>>3]&(uint8(1)<<uint8(7-(i%8))) > 0
return bA.Elems[i>>3]&(1<<uint8(7-(i%8))) > 0
}

// SetIndex sets the bit at index i within the bit array.
// The behavior is undefined if i >= bA.Count()
// SetIndex sets the bit at index i within the bit array. Returns true iff the operation
alessio marked this conversation as resolved.
Show resolved Hide resolved
// succeeded. The behavior is undefined if i >= bA.Count()
func (bA *CompactBitArray) SetIndex(i int, v bool) bool {
if bA == nil {
return false
Expand All @@ -62,9 +62,9 @@ func (bA *CompactBitArray) SetIndex(i int, v bool) bool {
}

if v {
bA.Elems[i>>3] |= (uint8(1) << uint8(7-(i%8)))
bA.Elems[i>>3] |= (1 << uint8(7-(i%8)))
} else {
bA.Elems[i>>3] &= ^(uint8(1) << uint8(7-(i%8)))
bA.Elems[i>>3] &= ^(1 << uint8(7-(i%8)))
}

return true
Expand All @@ -75,13 +75,22 @@ func (bA *CompactBitArray) SetIndex(i int, v bool) bool {
// there are two bits set to true before index 4.
func (bA *CompactBitArray) NumTrueBitsBefore(index int) int {
numTrueValues := 0
for i := 0; i < index; i++ {
if bA.GetIndex(i) {
numTrueValues++
max := bA.Count()
if index > max {
alessio marked this conversation as resolved.
Show resolved Hide resolved
index = max
}
var i = 0
for elem := 0; ; elem++ {
ValarDragon marked this conversation as resolved.
Show resolved Hide resolved
for b := 7; b >= 0; b-- {
if i >= index {
return numTrueValues
}
i++
if (bA.Elems[elem]>>b)&1 == 1 {
numTrueValues++
}
}
}

return numTrueValues
}

// Copy returns a copy of the provided bit array.
Expand All @@ -99,6 +108,18 @@ func (bA *CompactBitArray) Copy() *CompactBitArray {
}
}

// Equal checks if both bit arrays are equal. If both arrays are nil then it returns true.
func (bA *CompactBitArray) Equal(other *CompactBitArray) bool {
if bA == other {
return true
}
if bA == nil || other == nil {
return false
}
return bA.ExtraBitsStored == other.ExtraBitsStored &&
bytes.Equal(bA.Elems, other.Elems)
}

// String returns a string representation of CompactBitArray: BA{<bit-string>},
// where <bit-string> is a sequence of 'x' (1) and '_' (0).
// The <bit-string> includes spaces and newlines to help people.
Expand Down
38 changes: 38 additions & 0 deletions crypto/types/compact_bit_array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@ func TestNewBitArrayNeverCrashesOnNegatives(t *testing.T) {
}
}

func TestBitArrayEqual(t *testing.T) {
empty := new(CompactBitArray)
big1, _ := randCompactBitArray(1000)
big1Cpy := *big1
big2, _ := randCompactBitArray(1000)
big2.SetIndex(500, !big1.GetIndex(500)) // ensure they are different
cases := []struct {
name string
b1 *CompactBitArray
b2 *CompactBitArray
eq bool
}{
{name: "both nil are equal", b1: nil, b2: nil, eq: true},
{name: "if one is nil then not equal", b1: nil, b2: empty, eq: false},
{name: "nil and empty not equal", b1: empty, b2: nil, eq: false},
{name: "empty and empty equal", b1: empty, b2: new(CompactBitArray), eq: true},
{name: "same bits should be equal", b1: big1, b2: &big1Cpy, eq: true},
{name: "different should not be equal", b1: big1, b2: big2, eq: false},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
eq := tc.b1.Equal(tc.b2)
require.Equal(t, tc.eq, eq)
})
}
}

func TestJSONMarshalUnmarshal(t *testing.T) {

bA1 := NewCompactBitArray(0)
Expand Down Expand Up @@ -200,3 +228,13 @@ func TestCompactBitArrayGetSetIndex(t *testing.T) {
}
}
}

func BenchmarkNumTrueBitsBefore(b *testing.B) {
ba, _ := randCompactBitArray(100)

b.Run("new", func(b *testing.B) {
for i := 0; i < b.N; i++ {
ba.NumTrueBitsBefore(90)
}
})
}
3 changes: 2 additions & 1 deletion crypto/types/multisig/multisignature.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func getIndex(pk types.PubKey, keys []types.PubKey) int {
return -1
}

// AddSignature adds a signature to the multisig, at the corresponding index.
// AddSignature adds a signature to the multisig, at the corresponding index. The index must
// represent the pubkey index in the LegacyAmingPubKey structure, which verifies this signature.
// If the signature already exists, replace it.
func AddSignature(mSig *signing.MultiSignatureData, sig signing.SignatureData, index int) {
newSigIndex := mSig.BitArray.NumTrueBitsBefore(index)
Expand Down
11 changes: 1 addition & 10 deletions x/auth/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,7 @@ func sigDataEquals(data1, data2 signingtypes.SignatureData) bool {
if !ok {
return false
}

if data1.BitArray.ExtraBitsStored != data2.BitArray.ExtraBitsStored {
return false
}

if !bytes.Equal(data1.BitArray.Elems, data2.BitArray.Elems) {
return false
}

if len(data1.Signatures) != len(data2.Signatures) {
if !data1.BitArray.Equal(data2.BitArray) || len(data1.Signatures) != len(data2.Signatures) {
return false
}

Expand Down