Skip to content

Threshold Multisignature implementation#2164

Merged
ebuchman merged 12 commits intodevelopfrom
dev/multisig
Aug 14, 2018
Merged

Threshold Multisignature implementation#2164
ebuchman merged 12 commits intodevelopfrom
dev/multisig

Conversation

@ValarDragon
Copy link
Contributor

@ValarDragon ValarDragon commented Aug 7, 2018

This adds an implementation of the threshold multisig. I also merged develop in, for the change to the crypto.Signature interface. This is the only commit with new code changes: e7dd76c.

This may be easier to review if we merged the compact bitmap PR first, and then I aimed this PR at develop / remade this PR.

  • Updated all relevant documentation in docs
  • Updated all code comments where relevant
  • Wrote tests
  • Updated CHANGELOG_PENDING.md

@ValarDragon ValarDragon changed the title Dev/multisig Threshold Multisignature implementation Aug 7, 2018
@ValarDragon ValarDragon requested a review from liamsi August 7, 2018 02:26
@codecov-io
Copy link

codecov-io commented Aug 7, 2018

Codecov Report

Merging #2164 into develop will increase coverage by 0.12%.
The diff coverage is 72.5%.

@@             Coverage Diff             @@
##           develop    #2164      +/-   ##
===========================================
+ Coverage    62.23%   62.35%   +0.12%     
===========================================
  Files          212      215       +3     
  Lines        17567    17632      +65     
===========================================
+ Hits         10932    10994      +62     
  Misses        5725     5725              
- Partials       910      913       +3
Impacted Files Coverage Δ
crypto/multisig/wire.go 100% <100%> (ø)
crypto/multisig/compact_bit_array.go 77.77% <100%> (+1.2%) ⬆️
crypto/multisig/threshold_multisig.go 58.97% <58.97%> (ø)
crypto/multisig/multisignature.go 77.77% <77.77%> (ø)
rpc/grpc/client_server.go 66.66% <0%> (-4.77%) ⬇️
state/validation.go 52.32% <0%> (-3.93%) ⬇️
blockchain/pool.go 65.73% <0%> (-0.7%) ⬇️
abci/example/kvstore/persistent_kvstore.go 64.04% <0%> (ø) ⬆️
cmd/tendermint/commands/init.go 0% <0%> (ø) ⬆️
p2p/pex/file.go 78.04% <0%> (ø) ⬆️
... and 13 more

pubkeysCpy[3] = pubkeys[4]
multisigKey2 := NewThresholdMultiSignaturePubKey(2, pubkeysCpy)
require.NotEqual(t, multisigKey, multisigKey2)
require.False(t, multisigKey.Equals(multisigKey2))
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, I would also have expected this to call the underlying Equal instead

Copy link
Contributor

@liamsi liamsi left a comment

Choose a reason for hiding this comment

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

LGTM

@ebuchman
Copy link
Contributor

ebuchman commented Aug 8, 2018

Looks like there's some commit chaos here - can we get the relevant commits rebased on develop?

@ValarDragon
Copy link
Contributor Author

Absolutely! Let's merge the compact bit array pr first, that should simplify rebasing

@melekes melekes changed the base branch from dev/compact_bitmap to develop August 9, 2018 05:39
Copy link
Contributor

@melekes melekes left a comment

Choose a reason for hiding this comment

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

Needs to be rebased against latest develop.

Copy link
Contributor

@ebuchman ebuchman left a comment

Choose a reason for hiding this comment

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

Awesome work @ValarDragon. Lots of minor comments/questions about comments, code structure, and some additional checks we could do.

We should definitely up the tests and use tables, and figure out how we want to address the recursion.

Haven't reviewed the bit array yet - will do that next.

Happy for this to be merged if we open an issue to address this review - but we should address everything before it makes it to a release.

// Sigs is a list of signatures, sorted by corresponding index.
type Multisignature struct {
BitArray *CompactBitArray
Sigs [][]byte
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think of introducing a type Signature []byte instead of using []byte directly everywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you think that improves clarity, then sure. I didn't really see it as a problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

Leave for now. Maybe we'll open an issue for it later

return -1
}

// AddSignature adds a signature to the multisig, at the corresponding index.
Copy link
Contributor

Choose a reason for hiding this comment

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

// If the signature already exists, replace it.


var _ crypto.PubKey = &ThresholdMultiSignaturePubKey{}

// NewThresholdMultiSignaturePubKey returns a new ThresholdMultiSignaturePubKey.
Copy link
Contributor

Choose a reason for hiding this comment

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

// Panics if len(pubkeys) < k

Copy link
Contributor

Choose a reason for hiding this comment

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

I saw your comment about Go not having these panics. I think that's bad on their part. We should be as clear as possible to users about when we panic.


// NewThresholdMultiSignaturePubKey returns a new ThresholdMultiSignaturePubKey.
func NewThresholdMultiSignaturePubKey(k int, pubkeys []crypto.PubKey) crypto.PubKey {
if len(pubkeys) < k {
Copy link
Contributor

Choose a reason for hiding this comment

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

lets also check that k > 0 since the construction is meaningless otherwise and the uint will make k > len(pubkeys ) :P

}

// NewMultisig returns a new Multisignature of size n.
func NewMultisig(n int) *Multisignature {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we consider not using a pointer here? The methods could return new Multisignature, and the object becomes immutable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The object shouldn't be immutable? You want to call AddSignature to it repeatedly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Was suggesting AddSignature to return a new Multisignature, but yeh it's silly

return false
}
size := sig.BitArray.Size()
if len(sig.Sigs) < int(pk.K) || len(pk.Pubkeys) != size || sig.BitArray.NumOfTrueBitsBefore(size) < int(pk.K) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also do a consistency check that len(sig.Sigs) < size?

Maybe it would be better if we split this up and evaluate the concerns separately.

Like first we check the size of the bitarray against the number of sigs and the number of pubkeys.

Then we handle the K stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Theres no point in checking len(sig.Sigs) < size. k <= size. If k was > size, the sig would be impossible to verify. (I guess we can ban that case in marshalling / unmarshalling the multisig)

Copy link
Contributor

Choose a reason for hiding this comment

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

but we allow len(sigs.Sigs) > k, so we should set an upper bound.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh woops, your totally right, great catch!

"github.com/tendermint/tendermint/crypto/secp256k1"
)

func TestThresholdMultisig(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this test is ripe for table-driven testing.

We should be testing multiple values of N, and make sure we get expected behaviour when N<=0 and when N=1.

We should be testing multiple values of K, including that we get expected behaviour in the edges, eg. when K<=0, K=1, K=N-1, K=N, K>N

Copy link
Contributor

Choose a reason for hiding this comment

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

Same with the other tests really ...

Copy link
Contributor

Choose a reason for hiding this comment

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

We should also test what happens with recursive multisig pubkeys. Or if we want to restrict it, then test it fails.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should also test unmarshalling

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Table driven sounds good. I do think we should enable recursive multisigs.

Not really sure why we would test unmarshalling. We assume that amino works, and all variables are public?

Copy link
Contributor

Choose a reason for hiding this comment

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

Right

)

// TODO: Figure out API for others to either add their own pubkey types, or
// to make verify / marshal accept a cdc.
Copy link
Contributor

Choose a reason for hiding this comment

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

Huh. We don't really expose this as an API at all, which perhaps suggests that PubKey really could be a oneof

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'm confused. Don't we have to register the pubkey type in order to decode it? I'm not really sure what your suggesting. (Perhaps due to my unfamiliarity with oneof)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes I wasn't suggesting you do anything. But it goes back to the larger conversation of "do we really need pubkey to be an any-one-can-implement-it-without-permission interface" ie. the kind of thing Amino was built for, or would pure protobuf (using protobufs union type, called oneof) do just fine, since there isn't really a nice way to add new pubkey types right now without changing our crypto lib

Note this is unlike Msg in the SDK which really does want to be an interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I think pure protobuf is better here. We have to register it in all the codecs, so its not really like anyone can just implement it.

cdc.RegisterConcrete(ThresholdMultiSignaturePubKey{},
ThresholdPubkeyAminoRoute, nil)
cdc.RegisterConcrete(ed25519.PubKeyEd25519{},
ed25519.Ed25519PubKeyAminoRoute, nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

oh, should these just becomePubKeyAminoRoute since it's an anti-pattern to have the double prefix (pkg and var name)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, they should

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'll address this in a separate PR

Copy link
Contributor

Choose a reason for hiding this comment

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

Addressed in #2205

// TODO: Figure out API for others to either add their own pubkey types, or
// to make verify / marshal accept a cdc.
const (
ThresholdPubkeyAminoRoute = "tendermint/ThresholdMultisigPubkey"
Copy link
Contributor

Choose a reason for hiding this comment

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

We use eg. tendermint/PubKeyEd25519 so this should be tendermint/PubKeyMultisigThreshold

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

@ebuchman
Copy link
Contributor

Some review of the compact bit array PR: #2140 (review)

@ValarDragon
Copy link
Contributor Author

ValarDragon commented Aug 10, 2018

I think we should merge this. I can add more test cases and documentation for the recursive case in a separate PR. (I'll make those other tests table driven there as well. I made the main test table driven for what its aimed for, though I need to populate that table more as well)

The remaining things should be discussed in seperate issues or require seperate PR's.

@ValarDragon
Copy link
Contributor Author

Follow up comment made in an open issue!

// e.g. if bA = _XX_X_X, NumOfTrueBitsBefore(4) = 2, since
// the value at index 4 of the bit array is the third
// value that is true. (And it is 0-indexed)
func (bA *CompactBitArray) NumOfTrueBitsBefore(index int) int {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor point, I'd just call this NumTrueBitsBefore(index int). I think "of" is implied.

return true
}

// NumOfTrueBitsBefore returns the location of the given index, among the
Copy link
Contributor

Choose a reason for hiding this comment

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

"returns the location of the given index" is not the right wording since it doesn't return a location, but number of bits true.


// NumOfTrueBitsBefore returns the location of the given index, among the
// values in the bit array that are set to true.
// e.g. if bA = _XX_X_X, NumOfTrueBitsBefore(4) = 2, since
Copy link
Contributor

Choose a reason for hiding this comment

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

"since ... value at index 4 .... is true"
"since" seems like a strange modifier and the following statement a bit orthogonal.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it make sense to let the user specify 0 to count the total number of true bits?
And then we can comment that, which makes it clear that you don't want to enter NumTrueBitsBefore(0) when you really mean NumTrueBitsBefore(1).

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 feel like making edge cases like letting 0 counting the number of true bits makes it more confusing. I do definitely agree with your point about the comments here!

@melekes
Copy link
Contributor

melekes commented Aug 14, 2018

--> Running linter
crypto/multisig/compact_bit_array_test.go:173:44:warning: invalid operation: bA (variable of type *CompactBitArray) has no field or method NumOfTrueBitsBefore (gosimple)

# github.com/tendermint/tendermint/crypto/multisig
crypto/multisig/compact_bit_array_test.go:173:46: bA.NumOfTrueBitsBefore undefined (type *CompactBitArray has no field or method NumOfTrueBitsBefore)

@ebuchman ebuchman merged commit db53dc5 into develop Aug 14, 2018
@ebuchman ebuchman deleted the dev/multisig branch August 14, 2018 20:37
@ebuchman
Copy link
Contributor

Sorry I forgot to squash everything.

Follow up is in #2163 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants