Merge pull request #544 from nspcc-dev/fix/sign

consensus: sign and verify consensus payloads
This commit is contained in:
Roman Khimov 2019-12-06 12:05:17 +03:00 committed by GitHub
commit 5d2fb41991
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 21 deletions

2
go.mod
View file

@ -10,7 +10,7 @@ require (
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/mr-tron/base58 v1.1.2
github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055
github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254
github.com/nspcc-dev/rfc6979 v0.1.0
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect

4
go.sum
View file

@ -88,8 +88,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055 h1:Rs8zeSFfXOX2RuUjWwsUrQpNbxoWyeXU8lWPlYkxDo0=
github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055/go.mod h1:w1Ln2aT+dBlPhLnuZhBV+DfPEdS2CHWWLp5JTScY3bw=
github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254 h1:A4OkQDQOSPsJF8qUmqNvFDzmIGALrvOCZrMktllDoKc=
github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254/go.mod h1:w1Ln2aT+dBlPhLnuZhBV+DfPEdS2CHWWLp5JTScY3bw=
github.com/nspcc-dev/neofs-crypto v0.2.0 h1:ftN+59WqxSWz/RCgXYOfhmltOOqU+udsNQSvN6wkFck=
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
github.com/nspcc-dev/rfc6979 v0.1.0 h1:Lwg7esRRoyK1Up/IN1vAef1EmvrBeMHeeEkek2fAJ6c=

View file

@ -179,7 +179,10 @@ func getKeyPair(cfg *config.WalletConfig) (crypto.PrivateKey, crypto.PublicKey)
// OnPayload handles Payload receive.
func (s *service) OnPayload(cp *Payload) {
if s.cache.Has(cp.Hash()) {
if !cp.Verify() {
s.log.Debug("can't verify payload from #%d", cp.validatorIndex)
return
} else if s.cache.Has(cp.Hash()) {
return
}
@ -224,6 +227,10 @@ func (s *service) broadcast(p payload.ConsensusPayload) {
pr.minerTx = *s.txx.Get(pr.transactionHashes[0]).(*transaction.Transaction)
}
if err := p.(*Payload).Sign(s.dbft.Priv.(*privateKey)); err != nil {
s.log.Warnf("can't sign consensus payload: %v", err)
}
s.cache.Add(p)
s.Config.Broadcast(p.(*Payload))
}

View file

@ -27,6 +27,45 @@ func TestNewService(t *testing.T) {
require.Equal(t, tx, txx[1])
}
func TestService_OnPayload(t *testing.T) {
srv := newTestService(t)
priv, _ := getTestValidator(1)
p := new(Payload)
p.SetValidatorIndex(1)
p.SetPayload(&prepareRequest{})
// payload is not signed
srv.OnPayload(p)
shouldNotReceive(t, srv.messages)
require.Nil(t, srv.GetPayload(p.Hash()))
require.NoError(t, p.Sign(priv))
srv.OnPayload(p)
shouldReceive(t, srv.messages)
require.Equal(t, p, srv.GetPayload(p.Hash()))
// payload has already been received
srv.OnPayload(p)
shouldNotReceive(t, srv.messages)
}
func shouldReceive(t *testing.T, ch chan Payload) {
select {
case <-ch:
default:
require.Fail(t, "missing expected message")
}
}
func shouldNotReceive(t *testing.T, ch chan Payload) {
select {
case <-ch:
require.Fail(t, "unexpected message receive")
default:
}
}
func newTestService(t *testing.T) *service {
srv, err := NewService(Config{
Broadcast: func(*Payload) {},
@ -42,6 +81,38 @@ func newTestService(t *testing.T) *service {
return srv.(*service)
}
func getTestValidator(i int) (*privateKey, *publicKey) {
var wallet *config.WalletConfig
switch i {
case 0:
wallet = &config.WalletConfig{
Path: "6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y",
Password: "one",
}
case 1:
wallet = &config.WalletConfig{
Path: "6PYXHjPaNvW8YknSXaKsTWjf9FRxo1s4naV2jdmSQEgzaqKGX368rndN3L",
Password: "two",
}
case 2:
wallet = &config.WalletConfig{
Path: "6PYX86vYiHfUbpD95hfN1xgnvcSxy5skxfWYKu3ztjecxk6ikYs2kcWbeh",
Password: "three",
}
case 3:
wallet = &config.WalletConfig{
Path: "6PYRXVwHSqFSukL3CuXxdQ75VmsKpjeLgQLEjt83FrtHf1gCVphHzdD4nc",
Password: "four",
}
default:
return nil, nil
}
priv, pub := getKeyPair(wallet)
return priv.(*privateKey), pub.(*publicKey)
}
func newTestChain(t *testing.T) *core.Blockchain {
unitTestNetCfg, err := config.Load("../../config", config.ModeUnitTestNet)
require.NoError(t, err)

View file

@ -3,6 +3,7 @@ package consensus
import (
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/dbft/crypto"
"github.com/nspcc-dev/dbft/payload"
"github.com/pkg/errors"
)
@ -138,6 +139,10 @@ func (m *recoveryMessage) AddPayload(p payload.ConsensusPayload) {
m.prepareRequest = p.GetPrepareRequest().(*prepareRequest)
h := p.Hash()
m.preparationHash = &h
m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{
ValidatorIndex: p.ValidatorIndex(),
InvocationScript: p.(*Payload).Witness.InvocationScript,
})
case payload.PrepareResponseType:
m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{
ValidatorIndex: p.ValidatorIndex(),
@ -166,16 +171,33 @@ func (m *recoveryMessage) AddPayload(p payload.ConsensusPayload) {
}
// GetPrepareRequest implements payload.RecoveryMessage interface.
func (m *recoveryMessage) GetPrepareRequest(p payload.ConsensusPayload) payload.ConsensusPayload {
func (m *recoveryMessage) GetPrepareRequest(p payload.ConsensusPayload, validators []crypto.PublicKey, primary uint16) payload.ConsensusPayload {
if m.prepareRequest == nil {
return nil
}
return fromPayload(prepareRequestType, p.(*Payload), m.prepareRequest)
var compact *preparationCompact
for _, p := range m.preparationPayloads {
if p != nil && p.ValidatorIndex == primary {
compact = p
break
}
}
if compact == nil {
return nil
}
req := fromPayload(prepareRequestType, p.(*Payload), m.prepareRequest)
req.SetValidatorIndex(primary)
req.Witness.InvocationScript = compact.InvocationScript
req.Witness.VerificationScript = getVerificationScript(primary, validators)
return req
}
// GetPrepareResponses implements payload.RecoveryMessage interface.
func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload) []payload.ConsensusPayload {
func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload, validators []crypto.PublicKey) []payload.ConsensusPayload {
if m.preparationHash == nil {
return nil
}
@ -183,38 +205,49 @@ func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload) []payl
ps := make([]payload.ConsensusPayload, len(m.preparationPayloads))
for i, resp := range m.preparationPayloads {
ps[i] = fromPayload(prepareResponseType, p.(*Payload), &prepareResponse{
r := fromPayload(prepareResponseType, p.(*Payload), &prepareResponse{
preparationHash: *m.preparationHash,
})
ps[i].SetValidatorIndex(resp.ValidatorIndex)
r.SetValidatorIndex(resp.ValidatorIndex)
r.Witness.InvocationScript = resp.InvocationScript
r.Witness.VerificationScript = getVerificationScript(resp.ValidatorIndex, validators)
ps[i] = r
}
return ps
}
// GetChangeViews implements payload.RecoveryMessage interface.
func (m *recoveryMessage) GetChangeViews(p payload.ConsensusPayload) []payload.ConsensusPayload {
func (m *recoveryMessage) GetChangeViews(p payload.ConsensusPayload, validators []crypto.PublicKey) []payload.ConsensusPayload {
ps := make([]payload.ConsensusPayload, len(m.changeViewPayloads))
for i, cv := range m.changeViewPayloads {
ps[i] = fromPayload(changeViewType, p.(*Payload), &changeView{
c := fromPayload(changeViewType, p.(*Payload), &changeView{
newViewNumber: cv.OriginalViewNumber + 1,
timestamp: cv.Timestamp,
})
ps[i].SetValidatorIndex(cv.ValidatorIndex)
c.SetValidatorIndex(cv.ValidatorIndex)
c.Witness.InvocationScript = cv.InvocationScript
c.Witness.VerificationScript = getVerificationScript(cv.ValidatorIndex, validators)
ps[i] = c
}
return ps
}
// GetCommits implements payload.RecoveryMessage interface.
func (m *recoveryMessage) GetCommits(p payload.ConsensusPayload) []payload.ConsensusPayload {
func (m *recoveryMessage) GetCommits(p payload.ConsensusPayload, validators []crypto.PublicKey) []payload.ConsensusPayload {
ps := make([]payload.ConsensusPayload, len(m.commitPayloads))
for i, c := range m.commitPayloads {
cc := commit{signature: c.Signature}
ps[i] = fromPayload(commitType, p.(*Payload), &cc)
ps[i].SetValidatorIndex(c.ValidatorIndex)
cc := fromPayload(commitType, p.(*Payload), &commit{signature: c.Signature})
cc.SetValidatorIndex(c.ValidatorIndex)
cc.Witness.InvocationScript = c.InvocationScript
cc.Witness.VerificationScript = getVerificationScript(c.ValidatorIndex, validators)
ps[i] = cc
}
return ps
@ -230,6 +263,19 @@ func (m *recoveryMessage) SetPreparationHash(h *util.Uint256) {
m.preparationHash = h
}
func getVerificationScript(i uint16, validators []crypto.PublicKey) []byte {
if int(i) >= len(validators) {
return nil
}
pub, ok := validators[i].(*publicKey)
if !ok {
return nil
}
return pub.GetVerificationScript()
}
func fromPayload(t messageType, recovery *Payload, p io.Serializable) *Payload {
return &Payload{
message: message{

View file

@ -3,16 +3,28 @@ package consensus
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/dbft/crypto"
"github.com/nspcc-dev/dbft/payload"
"github.com/stretchr/testify/require"
)
func TestRecoveryMessage_Setters(t *testing.T) {
const size = 5
privs := getKeys(t, size)
pubs := make([]crypto.PublicKey, 5)
for i := range pubs {
pubs[i] = &publicKey{privs[i].PublicKey()}
}
r := &recoveryMessage{}
p := new(Payload)
p.SetType(payload.RecoveryMessageType)
p.SetPayload(r)
// sign payload to have verification script
require.NoError(t, p.Sign(privs[0]))
req := &prepareRequest{
timestamp: 87,
@ -24,6 +36,8 @@ func TestRecoveryMessage_Setters(t *testing.T) {
p1 := new(Payload)
p1.SetType(payload.PrepareRequestType)
p1.SetPayload(req)
p1.SetValidatorIndex(0)
require.NoError(t, p1.Sign(privs[0]))
t.Run("prepare response is added", func(t *testing.T) {
p2 := new(Payload)
@ -31,24 +45,28 @@ func TestRecoveryMessage_Setters(t *testing.T) {
p2.SetPayload(&prepareResponse{
preparationHash: p1.Hash(),
})
p2.SetValidatorIndex(1)
require.NoError(t, p2.Sign(privs[1]))
r.AddPayload(p2)
require.NotNil(t, r.PreparationHash())
require.Equal(t, p1.Hash(), *r.PreparationHash())
ps := r.GetPrepareResponses(p)
ps := r.GetPrepareResponses(p, pubs)
require.Len(t, ps, 1)
require.Equal(t, p2, ps[0])
require.True(t, ps[0].(*Payload).Verify())
})
t.Run("prepare request is added", func(t *testing.T) {
pr := r.GetPrepareRequest(p)
pr := r.GetPrepareRequest(p, pubs, p1.ValidatorIndex())
require.Nil(t, pr)
r.AddPayload(p1)
pr = r.GetPrepareRequest(p)
pr = r.GetPrepareRequest(p, pubs, p1.ValidatorIndex())
require.NotNil(t, pr)
require.Equal(t, p1, pr)
require.True(t, pr.(*Payload).Verify())
})
t.Run("change view is added", func(t *testing.T) {
@ -58,23 +76,42 @@ func TestRecoveryMessage_Setters(t *testing.T) {
newViewNumber: 1,
timestamp: 12345,
})
p3.SetValidatorIndex(3)
require.NoError(t, p3.Sign(privs[3]))
r.AddPayload(p3)
ps := r.GetChangeViews(p)
ps := r.GetChangeViews(p, pubs)
require.Len(t, ps, 1)
require.Equal(t, p3, ps[0])
require.True(t, ps[0].(*Payload).Verify())
})
t.Run("commit is added", func(t *testing.T) {
p4 := new(Payload)
p4.SetType(payload.CommitType)
p4.SetPayload(randomMessage(t, commitType))
p4.SetValidatorIndex(4)
require.NoError(t, p4.Sign(privs[4]))
r.AddPayload(p4)
ps := r.GetCommits(p)
ps := r.GetCommits(p, pubs)
require.Len(t, ps, 1)
require.Equal(t, p4, ps[0])
require.True(t, ps[0].(*Payload).Verify())
})
}
func getKeys(t *testing.T, n int) []*privateKey {
privs := make([]*privateKey, 0, n)
for i := 0; i < n; i++ {
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
require.NotNil(t, priv)
privs = append(privs, &privateKey{PrivateKey: priv})
}
return privs
}