diff --git a/privval/file.go b/privval/file.go index 213e1760e..cefbef376 100644 --- a/privval/file.go +++ b/privval/file.go @@ -369,6 +369,7 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { } signBytes := types.VoteSignBytes(chainID, vote) + extSignBytes := types.VoteExtensionSignBytes(chainID, vote) // We might crash before writing to the wal, // causing us to try to re-sign for the same HRS. @@ -398,18 +399,15 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { if err != nil { return err } - if err = pv.saveSigned(height, round, step, signBytes, sig); err != nil { + if err := pv.saveSigned(height, round, step, signBytes, sig); err != nil { return err } vote.Signature = sig - // Sign the vote extension, if any - if len(vote.Extension) > 0 { - var err error - vote.ExtensionSignature, err = pv.Key.PrivKey.Sign(vote.Extension) - if err != nil { - return err - } + // Sign the vote extension, regardless of whether it's present or not + vote.ExtensionSignature, err = pv.Key.PrivKey.Sign(extSignBytes) + if err != nil { + return err } return nil diff --git a/types/canonical.go b/types/canonical.go index 83250ae1e..203e70108 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -65,6 +65,19 @@ func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote } } +// CanonicalizeVoteExtension extracts the vote extension from the given vote +// and constructs a CanonicalizeVoteExtension struct, whose representation in +// bytes is what is signed in order to produce the vote extension's signature. +func CanonicalizeVoteExtension(chainID string, vote *tmproto.Vote) tmproto.CanonicalVoteExtension { + return tmproto.CanonicalVoteExtension{ + Extension: vote.Extension, + Height: vote.Height, + Round: vote.Round, + ChainId: chainID, + Address: vote.ValidatorAddress, + } +} + // CanonicalTime can be used to stringify time in a canonical way. func CanonicalTime(t time.Time) string { // Note that sending time over amino resets it to diff --git a/types/priv_validator.go b/types/priv_validator.go index a67e23779..06aaa9334 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -90,16 +90,15 @@ func (pv MockPV) SignVote(ctx context.Context, chainID string, vote *tmproto.Vot } signBytes := VoteSignBytes(useChainID, vote) + extSignBytes := VoteExtensionSignBytes(useChainID, vote) sig, err := pv.PrivKey.Sign(signBytes) if err != nil { return err } vote.Signature = sig - if len(vote.Extension) > 0 { - vote.ExtensionSignature, err = pv.PrivKey.Sign(vote.Extension) - if err != nil { - return err - } + vote.ExtensionSignature, err = pv.PrivKey.Sign(extSignBytes) + if err != nil { + return err } return nil } diff --git a/types/vote.go b/types/vote.go index ea205aa3a..301b5c8ba 100644 --- a/types/vote.go +++ b/types/vote.go @@ -103,6 +103,21 @@ func VoteSignBytes(chainID string, vote *tmproto.Vote) []byte { return bz } +// VoteExtensionSignBytes returns the proto-encoding of the canonicalized vote +// extension for signing. Panics if the marshaling fails. +// +// Similar to VoteSignBytes, the encoded Protobuf message is varint +// length-prefixed for backwards-compatibility with the Amino encoding. +func VoteExtensionSignBytes(chainID string, vote *tmproto.Vote) []byte { + pb := CanonicalizeVoteExtension(chainID, vote) + bz, err := protoio.MarshalDelimited(&pb) + if err != nil { + panic(err) + } + + return bz +} + func (vote *Vote) Copy() *Vote { voteCopy := *vote return &voteCopy