diff --git a/consensus/consensus.go b/consensus/consensus.go index 907386d94..8109cea02 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/binary" "encoding/hex" + "errors" "fmt" "reflect" "strconv" @@ -13,7 +14,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + protobuf "github.com/golang/protobuf/proto" "github.com/harmony-one/bls/ffi/go/bls" + consensus_proto "github.com/harmony-one/harmony/api/consensus" "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/types" bls_cosi "github.com/harmony-one/harmony/crypto/bls" @@ -192,6 +195,27 @@ func New(host p2p.Host, ShardID string, peers []p2p.Peer, leader p2p.Peer) *Cons return &consensus } +// Verify the signature of the message are valid from the signer's public key. +func verifyMessageSig(signerPubKey *bls.PublicKey, message consensus_proto.Message) error { + signature := message.Signature + message.Signature = nil + messageBytes, err := protobuf.Marshal(&message) + if err != nil { + return err + } + + msgSig := bls.Sign{} + err = msgSig.Deserialize(signature) + if err != nil { + return err + } + msgHash := sha256.Sum256(messageBytes) + if !msgSig.VerifyHash(signerPubKey, msgHash[:]) { + return errors.New("failed to verify the signature") + } + return nil +} + // Author returns the author of the block header. func (consensus *Consensus) Author(header *types.Header) (common.Address, error) { // TODO: implement this diff --git a/consensus/consensus_leader.go b/consensus/consensus_leader.go index 50181f6c2..4deaa1e2c 100644 --- a/consensus/consensus_leader.go +++ b/consensus/consensus_leader.go @@ -57,7 +57,7 @@ func (consensus *Consensus) WaitForNewBlock(blockChannel chan *types.Block) { // ProcessMessageLeader dispatches consensus message for the leader. func (consensus *Consensus) ProcessMessageLeader(payload []byte) { message := consensus_proto.Message{} - err := message.XXX_Unmarshal(payload) + err := protobuf.Unmarshal(payload, &message) if err != nil { utils.GetLogInstance().Error("Failed to unmarshal message payload.", "err", err, "consensus", consensus) @@ -93,7 +93,10 @@ func (consensus *Consensus) startConsensus(newBlock *types.Block) { // Set state to AnnounceDone consensus.state = AnnounceDone - // TODO: sign for leader itself + + // Leader sign the multi-sig itself + (*consensus.prepareSigs)[consensus.nodeID] = consensus.priKey.SignHash(consensus.blockHash[:]) + host.BroadcastMessageFromLeader(consensus.host, consensus.GetValidatorPeers(), msgToSend, consensus.OfflinePeers) } @@ -103,7 +106,6 @@ func (consensus *Consensus) processPrepareMessage(message consensus_proto.Messag blockHash := message.BlockHash validatorID := message.SenderId prepareSig := message.Payload - signature := message.Signature // Verify signature v, ok := consensus.validators.Load(validatorID) @@ -117,18 +119,12 @@ func (consensus *Consensus) processPrepareMessage(message consensus_proto.Messag return } - message.Signature = nil - messageBytes, err := protobuf.Marshal(&message) + // Verify message signature + err := verifyMessageSig(value.PubKey, message) if err != nil { - utils.GetLogInstance().Warn("Failed to marshal the prepare message", "error", err) + utils.GetLogInstance().Warn("Failed to verify the message signature", "Error", err, "validatorID", validatorID) + return } - _ = messageBytes - _ = signature - // TODO: verify message signature - //if schnorr.Verify(crypto.Ed25519Curve, value.PubKey, messageBytes, signature) != nil { - // consensus.Log.Warn("Received message with invalid signature", "validatorKey", consensus.leader.PubKey, "consensus", consensus) - // return - //} // check consensus Id consensus.mutex.Lock() @@ -180,6 +176,10 @@ func (consensus *Consensus) processPrepareMessage(message consensus_proto.Messag msgToSend, aggSig := consensus.constructPreparedMessage() consensus.aggregatedPrepareSig = aggSig + // Leader sign the multi-sig itself + // TODO: sign on the prepared multi-sig, rather than the block hash + (*consensus.commitSigs)[consensus.nodeID] = consensus.priKey.SignHash(consensus.blockHash[:]) + // Broadcast prepared message host.BroadcastMessageFromLeader(consensus.host, consensus.GetValidatorPeers(), msgToSend, consensus.OfflinePeers) @@ -194,7 +194,6 @@ func (consensus *Consensus) processCommitMessage(message consensus_proto.Message blockHash := message.BlockHash validatorID := message.SenderId commitSig := message.Payload - signature := message.Signature shouldProcess := true consensus.mutex.Lock() @@ -203,11 +202,11 @@ func (consensus *Consensus) processCommitMessage(message consensus_proto.Message // check consensus Id if consensusID != consensus.consensusID { shouldProcess = false - utils.GetLogInstance().Warn("Received Response with wrong consensus Id", "myConsensusId", consensus.consensusID, "theirConsensusId", consensusID, "consensus", consensus) + utils.GetLogInstance().Warn("Received Commit with wrong consensus Id", "myConsensusId", consensus.consensusID, "theirConsensusId", consensusID, "consensus", consensus) } if !bytes.Equal(blockHash, consensus.blockHash[:]) { - utils.GetLogInstance().Warn("Received Response with wrong blockHash", "myConsensusId", consensus.consensusID, "theirConsensusId", consensusID, "consensus", consensus) + utils.GetLogInstance().Warn("Received Commit with wrong blockHash", "myConsensusId", consensus.consensusID, "theirConsensusId", consensusID, "consensus", consensus) return } @@ -222,18 +221,13 @@ func (consensus *Consensus) processCommitMessage(message consensus_proto.Message utils.GetLogInstance().Warn("Invalid validator", "validatorID", validatorID, "consensus", consensus) return } - message.Signature = nil - messageBytes, err := protobuf.Marshal(&message) + + // Verify message signature + err := verifyMessageSig(value.PubKey, message) if err != nil { - utils.GetLogInstance().Warn("Failed to marshal the commit message", "error", err) + utils.GetLogInstance().Warn("Failed to verify the message signature", "Error", err, "validatorID", validatorID) + return } - _ = messageBytes - _ = signature - // TODO: verify message signature - //if schnorr.Verify(crypto.Ed25519Curve, value.PubKey, messageBytes, signature) != nil { - // consensus.Log.Warn("Received message with invalid signature", "validatorKey", consensus.leader.PubKey, "consensus", consensus) - // return - //} commitSigs := consensus.commitSigs commitBitmap := consensus.commitBitmap @@ -276,7 +270,7 @@ func (consensus *Consensus) processCommitMessage(message consensus_proto.Message host.BroadcastMessageFromLeader(consensus.host, consensus.GetValidatorPeers(), msgToSend, consensus.OfflinePeers) var blockObj types.Block - err = rlp.DecodeBytes(consensus.block, &blockObj) + err := rlp.DecodeBytes(consensus.block, &blockObj) if err != nil { utils.GetLogInstance().Debug("failed to construct the new block after consensus") } diff --git a/consensus/consensus_leader_test.go b/consensus/consensus_leader_test.go index 8e9f9e2cc..ec42d8a9a 100644 --- a/consensus/consensus_leader_test.go +++ b/consensus/consensus_leader_test.go @@ -10,6 +10,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/golang/mock/gomock" + protobuf "github.com/golang/protobuf/proto" + consensus_proto "github.com/harmony-one/harmony/api/consensus" "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/internal/utils" mock_host "github.com/harmony-one/harmony/p2p/host/mock" @@ -70,6 +72,57 @@ func TestProcessMessageLeaderPrepare(test *testing.T) { time.Sleep(1 * time.Second) } +func TestProcessMessageLeaderPrepareInvalidSignature(test *testing.T) { + ctrl := gomock.NewController(test) + defer ctrl.Finish() + + leader := p2p.Peer{IP: ip, Port: "7777"} + _, leader.PubKey = utils.GenKeyBLS(leader.IP, leader.Port) + + validators := make([]p2p.Peer, 3) + hosts := make([]p2p.Host, 3) + + for i := 0; i < 3; i++ { + port := fmt.Sprintf("%d", 7788+i) + validators[i] = p2p.Peer{IP: ip, Port: port, ValidatorID: i + 1} + _, validators[i].PubKey = utils.GenKeyBLS(validators[i].IP, validators[i].Port) + } + + m := mock_host.NewMockHost(ctrl) + // Asserts that the first and only call to Bar() is passed 99. + // Anything else will fail. + m.EXPECT().GetSelfPeer().Return(leader) + m.EXPECT().SendMessage(gomock.Any(), gomock.Any()).Times(0) + + consensusLeader := New(m, "0", validators, leader) + consensusLeader.blockHash = blockHash + + consensusValidators := make([]*Consensus, 3) + for i := 0; i < 3; i++ { + priKey, _, _ := utils.GenKeyP2P(validators[i].IP, validators[i].Port) + host, err := p2pimpl.NewHost(&validators[i], priKey) + if err != nil { + test.Fatalf("newhost error: %v", err) + } + hosts[i] = host + + consensusValidators[i] = New(hosts[i], "0", validators, leader) + consensusValidators[i].blockHash = blockHash + msg := consensusValidators[i].constructPrepareMessage() + + message := consensus_proto.Message{} + protobuf.Unmarshal(msg[1:], &message) + // Put invalid signature + message.Signature = consensusValidators[i].signMessage([]byte("random string")) + msg, _ = protobuf.Marshal(&message) + consensusLeader.ProcessMessageLeader(msg[1:]) + } + + assert.Equal(test, Finished, consensusLeader.state) + + time.Sleep(1 * time.Second) +} + func TestProcessMessageLeaderCommit(test *testing.T) { ctrl := gomock.NewController(test) defer ctrl.Finish() diff --git a/consensus/consensus_validator.go b/consensus/consensus_validator.go index a5043cfa3..5502a2071 100644 --- a/consensus/consensus_validator.go +++ b/consensus/consensus_validator.go @@ -39,7 +39,6 @@ func (consensus *Consensus) processAnnounceMessage(message consensus_proto.Messa blockHash := message.BlockHash leaderID := message.SenderId block := message.Payload - signature := message.Signature copy(consensus.blockHash[:], blockHash[:]) @@ -51,19 +50,12 @@ func (consensus *Consensus) processAnnounceMessage(message consensus_proto.Messa return } - // Verify signature - message.Signature = nil - messageBytes, err := protobuf.Marshal(&message) + // Verify message signature + err := verifyMessageSig(consensus.leader.PubKey, message) if err != nil { - utils.GetLogInstance().Warn("Failed to marshal the announce message", "error", err) + utils.GetLogInstance().Warn("Failed to verify the message signature", "Error", err, "leader ID", leaderID) + return } - _ = signature - _ = messageBytes - // TODO: verify message signature - //if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { - // consensus.Log.Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) - // return - //} // check block header is valid var blockObj types.Block @@ -117,7 +109,6 @@ func (consensus *Consensus) processPreparedMessage(message consensus_proto.Messa blockHash := message.BlockHash leaderID := message.SenderId messagePayload := message.Payload - signature := message.Signature //#### Read payload data offset := 0 @@ -139,19 +130,12 @@ func (consensus *Consensus) processPreparedMessage(message consensus_proto.Messa return } - // Verify signature - message.Signature = nil - messageBytes, err := protobuf.Marshal(&message) + // Verify message signature + err := verifyMessageSig(consensus.leader.PubKey, message) if err != nil { - utils.GetLogInstance().Warn("Failed to marshal the announce message", "error", err) + utils.GetLogInstance().Warn("Failed to verify the message signature", "Error", err, "leader ID", leaderID) + return } - _ = signature - _ = messageBytes - // TODO: verify message signature - //if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { - // consensus.Log.Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) - // return - //} // Add attack model of IncorrectResponse. if attack.GetInstance().IncorrectResponse() { @@ -188,7 +172,6 @@ func (consensus *Consensus) processCommittedMessage(message consensus_proto.Mess blockHash := message.BlockHash leaderID := message.SenderId messagePayload := message.Payload - signature := message.Signature //#### Read payload data offset := 0 @@ -210,19 +193,12 @@ func (consensus *Consensus) processCommittedMessage(message consensus_proto.Mess return } - // Verify signature - message.Signature = nil - messageBytes, err := protobuf.Marshal(&message) + // Verify message signature + err := verifyMessageSig(consensus.leader.PubKey, message) if err != nil { - utils.GetLogInstance().Warn("Failed to marshal the announce message", "error", err) + utils.GetLogInstance().Warn("Failed to verify the message signature", "Error", err, "leader ID", leaderID) + return } - _ = signature - _ = messageBytes - // TODO: verify message signature - //if schnorr.Verify(crypto.Ed25519Curve, consensus.leader.PubKey, messageBytes, signature) != nil { - // consensus.Log.Warn("Received message with invalid signature", "leaderKey", consensus.leader.PubKey, "consensus", consensus) - // return - //} // Add attack model of IncorrectResponse. if attack.GetInstance().IncorrectResponse() { diff --git a/consensus/consensus_validator_msg.go b/consensus/consensus_validator_msg.go index cf626babe..b26084568 100644 --- a/consensus/consensus_validator_msg.go +++ b/consensus/consensus_validator_msg.go @@ -24,7 +24,7 @@ func (consensus *Consensus) constructPrepareMessage() []byte { // 48 byte of bls signature sign := consensus.priKey.SignHash(message.BlockHash) if sign != nil { - message.Payload = consensus.priKey.SignHash(message.BlockHash).Serialize() + message.Payload = sign.Serialize() } marshaledMessage, err := protobuf.Marshal(&message) @@ -61,7 +61,7 @@ func (consensus *Consensus) constructCommitMessage() []byte { // TODO: sign on the prepared message hash, rather than the block hash sign := consensus.priKey.SignHash(message.BlockHash) if sign != nil { - message.Payload = consensus.priKey.SignHash(message.BlockHash).Serialize() + message.Payload = sign.Serialize() } marshaledMessage, err := protobuf.Marshal(&message)