From 99d9591e5f0af54bf06f41cfd2658cfcc9ee6436 Mon Sep 17 00:00:00 2001 From: Jimmy Hu Date: Fri, 10 Aug 2018 10:39:29 +0800 Subject: core: Add block hash signature functions in core/ctypto.go. (#39) --- core/crypto.go | 62 +++++++++++++++++++++++++++++++++++++ core/crypto_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++--- core/types/block.go | 12 ++++++++ core/types/validator.go | 29 ++++++++++++++---- 4 files changed, 174 insertions(+), 10 deletions(-) (limited to 'core') diff --git a/core/crypto.go b/core/crypto.go index 679b045..3bf8aa8 100644 --- a/core/crypto.go +++ b/core/crypto.go @@ -19,6 +19,7 @@ package core import ( "encoding/binary" + "sort" "github.com/dexon-foundation/dexon-consensus-core/common" "github.com/dexon-foundation/dexon-consensus-core/core/types" @@ -56,3 +57,64 @@ func verifyCompactionChainAckSignature(pubkey crypto.PublicKey, } return pubkey.VerifySignature(hash, sig), nil } + +func hashBlock(blockConv types.BlockConverter) (common.Hash, error) { + block := blockConv.Block() + binaryHeight := make([]byte, 8) + binary.LittleEndian.PutUint64(binaryHeight, block.Height) + // Handling Block.Acks. + acks := make(common.Hashes, 0, len(block.Acks)) + for ack := range block.Acks { + acks = append(acks, ack) + } + sort.Sort(acks) + binaryAcks := make([][]byte, len(block.Acks)) + for idx := range acks { + binaryAcks[idx] = acks[idx][:] + } + hashAcks := crypto.Keccak256Hash(binaryAcks...) + // Handle Block.Timestamps. + // TODO(jimmy-dexon): Store and get the sorted validatorIDs. + validators := make(types.ValidatorIDs, 0, len(block.Timestamps)) + for vID := range block.Timestamps { + validators = append(validators, vID) + } + sort.Sort(validators) + binaryTimestamps := make([][]byte, len(block.Timestamps)) + for idx, vID := range validators { + var err error + binaryTimestamps[idx], err = block.Timestamps[vID].MarshalBinary() + if err != nil { + return common.Hash{}, err + } + } + hashTimestamps := crypto.Keccak256Hash(binaryTimestamps...) + payloadHash := crypto.Keccak256Hash(blockConv.GetPayloads()...) + + hash := crypto.Keccak256Hash( + block.ProposerID.Hash[:], + block.ParentHash[:], + binaryHeight, + hashAcks[:], + hashTimestamps[:], + payloadHash[:]) + return hash, nil +} + +func signBlock(blockConv types.BlockConverter, + prv crypto.PrivateKey) (crypto.Signature, error) { + hash, err := hashBlock(blockConv) + if err != nil { + return crypto.Signature{}, err + } + return prv.Sign(hash) +} + +func verifyBlockSignature(pubkey crypto.PublicKey, + blockConv types.BlockConverter, sig crypto.Signature) (bool, error) { + hash, err := hashBlock(blockConv) + if err != nil { + return false, err + } + return pubkey.VerifySignature(hash, sig), nil +} diff --git a/core/crypto_test.go b/core/crypto_test.go index 0ee28e4..be130e2 100644 --- a/core/crypto_test.go +++ b/core/crypto_test.go @@ -32,10 +32,28 @@ type CryptoTestSuite struct { suite.Suite } -func (s *CryptoTestSuite) newBlock(prevBlock *types.Block) *types.Block { +var myVID = types.ValidatorID{Hash: common.NewRandomHash()} + +type simpleBlock struct { + block *types.Block +} + +func (sb *simpleBlock) Block() *types.Block { + return sb.block +} + +func (sb *simpleBlock) GetPayloads() [][]byte { + return [][]byte{} +} + +func (s *CryptoTestSuite) prepareBlock(prevBlock *types.Block) *types.Block { + acks := make(map[common.Hash]struct{}) + timestamps := make(map[types.ValidatorID]time.Time) + timestamps[myVID] = time.Now().UTC() if prevBlock == nil { return &types.Block{ - Hash: common.NewRandomHash(), + Acks: acks, + Timestamps: timestamps, ConsensusInfo: types.ConsensusInfo{ Timestamp: time.Now(), Height: 0, @@ -44,9 +62,14 @@ func (s *CryptoTestSuite) newBlock(prevBlock *types.Block) *types.Block { } parentHash, err := hashCompactionChainAck(prevBlock) s.Require().Nil(err) + s.Require().NotEqual(prevBlock.Hash, common.Hash{}) + acks[parentHash] = struct{}{} return &types.Block{ - Hash: common.NewRandomHash(), + ParentHash: prevBlock.Hash, + Acks: acks, + Timestamps: timestamps, ConsensusInfoParentHash: parentHash, + Height: prevBlock.Height + 1, CompactionChainAck: types.CompactionChainAck{ AckingBlockHash: prevBlock.Hash, }, @@ -57,6 +80,14 @@ func (s *CryptoTestSuite) newBlock(prevBlock *types.Block) *types.Block { } } +func (s *CryptoTestSuite) newBlock(prevBlock *types.Block) *types.Block { + block := s.prepareBlock(prevBlock) + var err error + block.Hash, err = hashBlock(&simpleBlock{block: block}) + s.Require().Nil(err) + return block +} + func (s *CryptoTestSuite) generateCompactionChain( length int, prv crypto.PrivateKey) []*types.Block { blocks := make([]*types.Block, length) @@ -76,7 +107,7 @@ func (s *CryptoTestSuite) generateCompactionChain( return blocks } -func (s *CryptoTestSuite) TestSignature() { +func (s *CryptoTestSuite) TestCompactionChainAckSignature() { prv, err := eth.NewPrivateKey() pub := prv.PublicKey() s.Require().Nil(err) @@ -111,6 +142,48 @@ func (s *CryptoTestSuite) TestSignature() { } } +func (s *CryptoTestSuite) generateBlockChain( + length int, prv crypto.PrivateKey) []*types.Block { + blocks := make([]*types.Block, length) + var prevBlock *types.Block + for idx := range blocks { + block := s.newBlock(prevBlock) + blocks[idx] = block + var err error + block.Signature, err = signBlock(&simpleBlock{block: block}, prv) + s.Require().Nil(err) + } + return blocks +} + +func (s *CryptoTestSuite) TestBlockSignature() { + prv, err := eth.NewPrivateKey() + pub := prv.PublicKey() + s.Require().Nil(err) + blocks := s.generateBlockChain(10, prv) + blockMap := make(map[common.Hash]*types.Block) + for _, block := range blocks { + blockMap[block.Hash] = block + } + for _, block := range blocks { + if !block.IsGenesis() { + parentBlock, exist := blockMap[block.ParentHash] + s.Require().True(exist) + s.True(parentBlock.Height == block.Height-1) + hash, err := hashBlock(&simpleBlock{block: parentBlock}) + s.Require().Nil(err) + s.Equal(hash, block.ParentHash) + } + s.True(verifyBlockSignature(pub, &simpleBlock{block: block}, block.Signature)) + } + // Modify Block.Acks and verify signature again. + for _, block := range blocks { + block.Acks[common.NewRandomHash()] = struct{}{} + s.False(verifyBlockSignature( + pub, &simpleBlock{block: block}, block.Signature)) + } +} + func TestCrypto(t *testing.T) { suite.Run(t, new(CryptoTestSuite)) } diff --git a/core/types/block.go b/core/types/block.go index e35ab8d..363de3a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -60,6 +60,7 @@ type Block struct { Timestamps map[ValidatorID]time.Time `json:"timestamps"` Acks map[common.Hash]struct{} `json:"acks"` CompactionChainAck CompactionChainAck `json:"compaction_chain_ack"` + Signature crypto.Signature `json:"signature"` ConsensusInfo ConsensusInfo `json:"consensus_info"` // ConsensusInfoParentHash is the hash value of Block.ConsensusInfoParentHash @@ -72,10 +73,21 @@ type Block struct { Status Status `json:"-"` } +// Block implements BlockConverter interface. +func (b *Block) Block() *Block { + return b +} + +// GetPayloads impelmemnts BlockConverter interface. +func (b *Block) GetPayloads() [][]byte { + return [][]byte{} +} + // BlockConverter interface define the interface for extracting block // information from an existing object. type BlockConverter interface { Block() *Block + GetPayloads() [][]byte } func (b *Block) String() string { diff --git a/core/types/validator.go b/core/types/validator.go index 48ce586..86c3acc 100644 --- a/core/types/validator.go +++ b/core/types/validator.go @@ -1,15 +1,15 @@ // Copyright 2018 The dexon-consensus-core Authors // This file is part of the dexon-consensus-core library. // -// The dexon-consensus-core library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License as +// The dexon-consensus-core library is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 of the License, // or (at your option) any later version. // -// The dexon-consensus-core library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. +// The dexon-consensus-core library is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the dexon-consensus-core library. If not, see @@ -18,6 +18,8 @@ package types import ( + "bytes" + "github.com/dexon-foundation/dexon-consensus-core/common" ) @@ -25,3 +27,18 @@ import ( type ValidatorID struct { common.Hash } + +// ValidatorIDs implements sort.Interface for ValidatorID. +type ValidatorIDs []ValidatorID + +func (v ValidatorIDs) Len() int { + return len(v) +} + +func (v ValidatorIDs) Less(i int, j int) bool { + return bytes.Compare([]byte(v[i].Hash[:]), []byte(v[j].Hash[:])) == -1 +} + +func (v ValidatorIDs) Swap(i int, j int) { + v[i], v[j] = v[j], v[i] +} -- cgit v1.2.3