From 65e0675bd6841e78e6818610e09bbbaaf13420b6 Mon Sep 17 00:00:00 2001 From: Jason Yellick Date: Thu, 5 Jan 2017 15:12:47 -0500 Subject: [PATCH] [FAB-1528] Add block signature https://jira.hyperledger.org/browse/FAB-1528 This changeset adds a signing interface which the MSP team has agreed to implement. It also adds a single signature for the block creation (which will be adequate for Solo/Kafka but will need to be augmented for SBFT). It also adds the signature to the last configuration item. Change-Id: I87f37b3e4ff523f7a32ccdf2e87a80cc9ca7f87a Signed-off-by: Jason Yellick --- orderer/mocks/multichain/multichain.go | 10 ++++ orderer/multichain/chainsupport.go | 71 +++++++++++++++++++++++-- orderer/multichain/chainsupport_test.go | 35 +++++++++++- orderer/multichain/manager.go | 23 ++++++-- protos/common/block.go | 7 ++- protos/utils/blockutils.go | 13 ++++- 6 files changed, 145 insertions(+), 14 deletions(-) diff --git a/orderer/mocks/multichain/multichain.go b/orderer/mocks/multichain/multichain.go index d5bbcd0b1f4..447f2cc0aba 100644 --- a/orderer/mocks/multichain/multichain.go +++ b/orderer/mocks/multichain/multichain.go @@ -83,3 +83,13 @@ func (mcs *ConsenterSupport) WriteBlock(block *cb.Block, _committers []filter.Co func (mcs *ConsenterSupport) ChainID() string { return mcs.ChainIDVal } + +// Sign returns the bytes passed in +func (mcs *ConsenterSupport) Sign(message []byte) []byte { + return message +} + +// NewSignatureHeader returns an empty signature header +func (mcs *ConsenterSupport) NewSignatureHeader() *cb.SignatureHeader { + return &cb.SignatureHeader{} +} diff --git a/orderer/multichain/chainsupport.go b/orderer/multichain/chainsupport.go index 52d4b464bcb..7374bbdd51c 100644 --- a/orderer/multichain/chainsupport.go +++ b/orderer/multichain/chainsupport.go @@ -58,6 +58,7 @@ type Chain interface { // ConsenterSupport provides the resources available to a Consenter implementation type ConsenterSupport interface { + Signer BlockCutter() blockcutter.Receiver SharedConfig() sharedconfig.Manager CreateNextBlock(messages []*cb.Envelope) *cb.Block @@ -83,6 +84,7 @@ type chainSupport struct { sharedConfigManager sharedconfig.Manager ledger rawledger.ReadWriter filters *filter.RuleSet + signer Signer lastConfiguration uint64 lastConfigSeq uint64 } @@ -94,6 +96,7 @@ func newChainSupport( backing rawledger.ReadWriter, sharedConfigManager sharedconfig.Manager, consenters map[string]Consenter, + signer Signer, ) *chainSupport { cutter := blockcutter.NewReceiverImpl(sharedConfigManager, filters) @@ -110,6 +113,7 @@ func newChainSupport( cutter: cutter, filters: filters, ledger: backing, + signer: signer, } var err error @@ -145,6 +149,14 @@ func (cs *chainSupport) start() { cs.chain.Start() } +func (cs *chainSupport) NewSignatureHeader() *cb.SignatureHeader { + return cs.signer.NewSignatureHeader() +} + +func (cs *chainSupport) Sign(message []byte) []byte { + return cs.signer.Sign(message) +} + func (cs *chainSupport) SharedConfig() sharedconfig.Manager { return cs.sharedConfigManager } @@ -181,21 +193,70 @@ func (cs *chainSupport) CreateNextBlock(messages []*cb.Envelope) *cb.Block { return rawledger.CreateNextBlock(cs.ledger, messages) } -func (cs *chainSupport) WriteBlock(block *cb.Block, committers []filter.Committer) *cb.Block { - for _, committer := range committers { - committer.Commit() +// TODO, factor this out into common util code +func metadataSignatureBytes(value []byte, sigHeader []byte, blockHeader []byte) []byte { + result := make([]byte, len(value)+len(sigHeader)+len(blockHeader)) + last := 0 + for _, slice := range [][]byte{value, sigHeader, blockHeader} { + for i := range slice { + result[i+last] = slice[i] + } + last += len(slice) } + return result +} + +func (cs *chainSupport) addBlockSignature(block *cb.Block) { + logger.Debugf("%+v", cs) + logger.Debugf("%+v", cs.signer) + blockSignature := &cb.MetadataSignature{ + SignatureHeader: utils.MarshalOrPanic(cs.signer.NewSignatureHeader()), + } + + // Note, this value is intentionally nil, as this metadata is only about the signature, there is no additional metadata + // information required beyond the fact that the metadata item is signed. + blockSignatureValue := []byte(nil) + + blockSignature.Signature = cs.signer.Sign(metadataSignatureBytes(blockSignatureValue, blockSignature.SignatureHeader, block.Header.Bytes())) + + block.Metadata.Metadata[cb.BlockMetadataIndex_SIGNATURES] = utils.MarshalOrPanic(&cb.Metadata{ + Value: blockSignatureValue, + Signatures: []*cb.MetadataSignature{ + blockSignature, + }, + }) +} +func (cs *chainSupport) addLastConfigSignature(block *cb.Block) { configSeq := cs.configManager.Sequence() if configSeq > cs.lastConfigSeq { cs.lastConfiguration = block.Header.Number cs.lastConfigSeq = configSeq } + lastConfigSignature := &cb.MetadataSignature{ + SignatureHeader: utils.MarshalOrPanic(cs.signer.NewSignatureHeader()), + } + + lastConfigValue := utils.MarshalOrPanic(&cb.LastConfiguration{Index: cs.lastConfiguration}) + + lastConfigSignature.Signature = cs.signer.Sign(metadataSignatureBytes(lastConfigValue, lastConfigSignature.SignatureHeader, block.Header.Bytes())) + block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIGURATION] = utils.MarshalOrPanic(&cb.Metadata{ - Value: utils.MarshalOrPanic(&cb.LastConfiguration{Index: cs.lastConfiguration}), - // XXX Add signature once signing is available + Value: lastConfigValue, + Signatures: []*cb.MetadataSignature{ + lastConfigSignature, + }, }) +} + +func (cs *chainSupport) WriteBlock(block *cb.Block, committers []filter.Committer) *cb.Block { + for _, committer := range committers { + committer.Commit() + } + + cs.addBlockSignature(block) + cs.addLastConfigSignature(block) err := cs.ledger.Append(block) if err != nil { diff --git a/orderer/multichain/chainsupport_test.go b/orderer/multichain/chainsupport_test.go index 5507caf0a00..7b48c50d0dd 100644 --- a/orderer/multichain/chainsupport_test.go +++ b/orderer/multichain/chainsupport_test.go @@ -17,6 +17,7 @@ limitations under the License. package multichain import ( + "bytes" "reflect" "testing" @@ -64,7 +65,7 @@ func (mc *mockCommitter) Commit() { func TestCommitConfig(t *testing.T) { ml := &mockLedgerReadWriter{} cm := &mockconfigtx.Manager{} - cs := &chainSupport{ledger: ml, configManager: cm} + cs := &chainSupport{ledger: ml, configManager: cm, signer: &xxxCryptoHelper{}} txs := []*cb.Envelope{makeNormalTx("foo", 0), makeNormalTx("bar", 1)} committers := []filter.Committer{&mockCommitter{}, &mockCommitter{}} block := cs.CreateNextBlock(txs) @@ -86,10 +87,40 @@ func TestCommitConfig(t *testing.T) { } } +func TestMetadataSignatureBytes(t *testing.T) { + value := []byte("Value") + signatureHeader := []byte("SignatureHeader") + blockHeader := []byte("BlockHeader") + + sigBytes := metadataSignatureBytes(value, signatureHeader, blockHeader) + expected := []byte("ValueSignatureHeaderBlockHeader") + if !bytes.Equal(sigBytes, expected) { + t.Errorf("Did not compute first signature bytes correctly, expected %s, got %s", expected, sigBytes) + } +} + +func TestWriteBlockSignatures(t *testing.T) { + ml := &mockLedgerReadWriter{} + cm := &mockconfigtx.Manager{} + cs := &chainSupport{ledger: ml, configManager: cm, signer: &xxxCryptoHelper{}} + + blockMetadata := func(block *cb.Block) *cb.Metadata { + metadata, err := utils.GetMetadataFromBlock(block, cb.BlockMetadataIndex_SIGNATURES) + if err != nil { + panic(err) + } + return metadata + } + + if blockMetadata(cs.WriteBlock(cb.NewBlock(0, nil), nil)) == nil { + t.Fatalf("Block should have block signature") + } +} + func TestWriteLastConfiguration(t *testing.T) { ml := &mockLedgerReadWriter{} cm := &mockconfigtx.Manager{} - cs := &chainSupport{ledger: ml, configManager: cm} + cs := &chainSupport{ledger: ml, configManager: cm, signer: &xxxCryptoHelper{}} lastConfig := func(block *cb.Block) uint64 { index, err := utils.GetLastConfigurationIndexFromBlock(block) diff --git a/orderer/multichain/manager.go b/orderer/multichain/manager.go index 2cf7440c80c..38ad06d001b 100644 --- a/orderer/multichain/manager.go +++ b/orderer/multichain/manager.go @@ -41,6 +41,23 @@ func (xxx xxxCryptoHelper) VerifySignature(sd *cb.SignedData) error { return nil } +func (xxx xxxCryptoHelper) NewSignatureHeader() *cb.SignatureHeader { + return &cb.SignatureHeader{} +} + +func (xxx xxxCryptoHelper) Sign(message []byte) []byte { + return message +} + +// Signer is a temporary stub interface which will be implemented by the local MSP +type Signer interface { + // NewSignatureHeader creates a SignatureHeader with the correct signing identity and a valid nonce + NewSignatureHeader() *cb.SignatureHeader + + // Sign a message which should embed a signature header created by NewSignatureHeader + Sign(message []byte) []byte +} + // Manager coordinates the creation and access of chains type Manager interface { // GetChain retrieves the chain support for a chain (and whether it exists) @@ -99,14 +116,14 @@ func NewManagerImpl(ledgerFactory rawledger.Factory, consenters map[string]Conse logger.Fatalf("There appear to be two system chains %s and %s", ml.sysChain.support.ChainID(), chainID) } logger.Debugf("Starting with system chain: %s", chainID) - chain := newChainSupport(createSystemChainFilters(ml, configManager), configManager, policyManager, backingLedger, sharedConfigManager, consenters) + chain := newChainSupport(createSystemChainFilters(ml, configManager), configManager, policyManager, backingLedger, sharedConfigManager, consenters, &xxxCryptoHelper{}) ml.chains[string(chainID)] = chain ml.sysChain = newSystemChain(chain) // We delay starting this chain, as it might try to copy and replace the chains map via newChain before the map is fully built defer chain.start() } else { logger.Debugf("Starting chain: %s", chainID) - chain := newChainSupport(createStandardFilters(configManager), configManager, policyManager, backingLedger, sharedConfigManager, consenters) + chain := newChainSupport(createStandardFilters(configManager), configManager, policyManager, backingLedger, sharedConfigManager, consenters, &xxxCryptoHelper{}) ml.chains[string(chainID)] = chain chain.start() } @@ -208,7 +225,7 @@ func (ml *multiLedger) newChain(configtx *cb.Envelope) { newChains[key] = value } - cs := newChainSupport(createStandardFilters(configManager), configManager, policyManager, backingLedger, sharedConfig, ml.consenters) + cs := newChainSupport(createStandardFilters(configManager), configManager, policyManager, backingLedger, sharedConfig, ml.consenters, &xxxCryptoHelper{}) chainID := configManager.ChainID() logger.Debugf("Created and starting new chain %s", chainID) diff --git a/protos/common/block.go b/protos/common/block.go index 5155ca85b9f..cb6937384d6 100644 --- a/protos/common/block.go +++ b/protos/common/block.go @@ -34,13 +34,16 @@ func NewBlock(seqNum uint64, previousHash []byte) *Block { return block } -func (b *BlockHeader) Hash() []byte { +func (b *BlockHeader) Bytes() []byte { data, err := proto.Marshal(b) // XXX this is wrong, protobuf is not the right mechanism to serialize for a hash if err != nil { panic("This should never fail and is generally irrecoverable") } + return data +} - return util.ComputeCryptoHash(data) +func (b *BlockHeader) Hash() []byte { + return util.ComputeCryptoHash(b.Bytes()) } func (b *BlockData) Hash() []byte { diff --git a/protos/utils/blockutils.go b/protos/utils/blockutils.go index 975cbbcc630..74621fe0ef9 100644 --- a/protos/utils/blockutils.go +++ b/protos/utils/blockutils.go @@ -48,10 +48,19 @@ func GetChainIDFromBlock(block *cb.Block) (string, error) { return payload.Header.ChainHeader.ChainID, nil } +// GetMetadataFromBlock retrieves metadata at the specified index +func GetMetadataFromBlock(block *cb.Block, index cb.BlockMetadataIndex) (*cb.Metadata, error) { + md := &cb.Metadata{} + err := proto.Unmarshal(block.Metadata.Metadata[index], md) + if err != nil { + return nil, err + } + return md, nil +} + // GetLastConfigurationIndexFromBlock retrieves the index of the last configuration block as encoded in the block metadata func GetLastConfigurationIndexFromBlock(block *cb.Block) (uint64, error) { - md := &cb.Metadata{} - err := proto.Unmarshal(block.Metadata.Metadata[cb.BlockMetadataIndex_LAST_CONFIGURATION], md) + md, err := GetMetadataFromBlock(block, cb.BlockMetadataIndex_LAST_CONFIGURATION) if err != nil { return 0, err }