Skip to content

Commit

Permalink
BFT Block Puller: verify attestation (#4242)
Browse files Browse the repository at this point in the history
Change-Id: Ia34ff4536453581895c4f13b2d73f0e7066ce125

Signed-off-by: Yoav Tock <tock@il.ibm.com>
  • Loading branch information
tock-ibm authored May 30, 2023
1 parent 8ab6251 commit 7143cd2
Show file tree
Hide file tree
Showing 17 changed files with 287 additions and 61 deletions.
5 changes: 5 additions & 0 deletions gossip/api/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ type MessageCryptoService interface {
// else returns error
VerifyBlock(channelID common.ChannelID, seqNum uint64, block *cb.Block) error

// VerifyBlockAttestation does the same as VerifyBlock, except it assumes block.Data = nil. It therefore does not
// compute the block.Data.Hash() and compare it to the block.Header.DataHash. This is used when the orderer
// delivers a block with header & metadata only, as an attestation of block existence.
VerifyBlockAttestation(channelID string, block *cb.Block) error

// Sign signs msg with this peer's signing key and outputs
// the signature if no error occurred.
Sign(msg []byte) ([]byte, error)
Expand Down
6 changes: 6 additions & 0 deletions gossip/comm/comm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ func (*naiveSecProvider) VerifyBlock(channelID common.ChannelID, seqNum uint64,
return nil
}

// VerifyBlockAttestation returns nil if the block attestation is properly signed,
// else returns error
func (*naiveSecProvider) VerifyBlockAttestation(channelID string, signedBlock *cb.Block) error {
return nil
}

// Sign signs msg with this peer's signing key and outputs
// the signature if no error occurred.
func (*naiveSecProvider) Sign(msg []byte) ([]byte, error) {
Expand Down
4 changes: 4 additions & 0 deletions gossip/gossip/channel/channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ func (cs *cryptoService) VerifyBlock(channelID common.ChannelID, seqNum uint64,
return args.Get(0).(error)
}

func (*cryptoService) VerifyBlockAttestation(channelID string, signedBlock *cb.Block) error {
panic("Should not be called in this test")
}

func (cs *cryptoService) Sign(msg []byte) ([]byte, error) {
panic("Should not be called in this test")
}
Expand Down
6 changes: 6 additions & 0 deletions gossip/gossip/gossip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ func (*naiveCryptoService) VerifyBlock(channelID common.ChannelID, seqNum uint64
return nil
}

// VerifyBlockAttestation returns nil if the block attestation is properly signed,
// else returns error
func (*naiveCryptoService) VerifyBlockAttestation(channelID string, signedBlock *cb.Block) error {
return nil
}

// Sign signs msg with this peer's signing key and outputs
// the signature if no error occurred.
func (*naiveCryptoService) Sign(msg []byte) ([]byte, error) {
Expand Down
6 changes: 6 additions & 0 deletions gossip/gossip/orgs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ func (*configurableCryptoService) VerifyBlock(channelID common.ChannelID, seqNum
return nil
}

// VerifyBlockAttestation returns nil if the block attestation is properly signed,
// else returns error
func (*configurableCryptoService) VerifyBlockAttestation(channelID string, signedBlock *cb.Block) error {
return nil
}

// Sign signs msg with this peer's signing key and outputs
// the signature if no error occurred.
func (*configurableCryptoService) Sign(msg []byte) ([]byte, error) {
Expand Down
6 changes: 6 additions & 0 deletions gossip/identity/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ func (*naiveCryptoService) VerifyBlock(channelID common.ChannelID, seqNum uint64
return nil
}

// VerifyBlockAttestation returns nil if the block attestation is properly signed,
// else returns error
func (*naiveCryptoService) VerifyBlockAttestation(channelID string, signedBlock *cb.Block) error {
return nil
}

// VerifyByChannel verifies a peer's signature on a message in the context
// of a specific channel
func (*naiveCryptoService) VerifyByChannel(_ common.ChannelID, _ api.PeerIdentityType, _, _ []byte) error {
Expand Down
6 changes: 6 additions & 0 deletions gossip/service/gossip_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,12 @@ func (*naiveCryptoService) VerifyBlock(chainID gossipcommon.ChannelID, seqNum ui
return nil
}

// VerifyBlockAttestation returns nil if the block attestation is properly signed,
// else returns error
func (*naiveCryptoService) VerifyBlockAttestation(channelID string, signedBlock *common.Block) error {
return nil
}

// Sign signs msg with this peer's signing key and outputs
// the signature if no error occurred.
func (*naiveCryptoService) Sign(msg []byte) ([]byte, error) {
Expand Down
6 changes: 6 additions & 0 deletions gossip/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ func (*cryptoServiceMock) VerifyBlock(channelID common.ChannelID, seqNum uint64,
return nil
}

// VerifyBlockAttestation returns nil if the block attestation is properly signed,
// else returns error
func (*cryptoServiceMock) VerifyBlockAttestation(channelID string, signedBlock *pcomm.Block) error {
return nil
}

// Sign signs msg with this peer's signing key and outputs
// the signature if no error occurred.
func (*cryptoServiceMock) Sign(msg []byte) ([]byte, error) {
Expand Down
23 changes: 23 additions & 0 deletions internal/peer/gossip/mcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ func (s *MSPMessageCryptoService) VerifyBlock(chainID common.ChannelID, seqNum u
return fmt.Errorf("Header.DataHash is different from Hash(block.Data) for block with id [%d] on channel [%s]", block.Header.Number, chainID)
}

return s.verifyHeaderAndMetadata(channelID, block)
}

func (s *MSPMessageCryptoService) verifyHeaderAndMetadata(channelID string, block *pcommon.Block) error {
// Get the policy manager for channelID
cpm := s.channelPolicyManagerGetter.Manager(channelID)
if cpm == nil {
Expand Down Expand Up @@ -184,6 +188,25 @@ func (s *MSPMessageCryptoService) VerifyBlock(chainID common.ChannelID, seqNum u
return verifier(block.Header, block.Metadata)
}

// VerifyBlockAttestation returns nil when the header matches the metadata signature. It assumed the block.Data is nil
// and therefore does not verify that Header.DataHash is equal to the hash of block.Data. This is used when the orderer
// delivers a block with header & metadata only, as an attestation of block existence.
func (s *MSPMessageCryptoService) VerifyBlockAttestation(chainID string, block *pcommon.Block) error {
if block == nil {
return fmt.Errorf("Invalid Block on channel [%s]. Block is nil.", chainID)
}
if block.Header == nil {
return fmt.Errorf("Invalid Block on channel [%s]. Header must be different from nil.", chainID)
}

// - Unmarshal medatada
if block.Metadata == nil || len(block.Metadata.Metadata) == 0 {
return fmt.Errorf("Block with id [%d] on channel [%s] does not have metadata. Block not valid.", block.Header.Number, chainID)
}

return s.verifyHeaderAndMetadata(chainID, block)
}

// Sign signs msg with this peer's signing key and outputs
// the signature if no error occurred.
func (s *MSPMessageCryptoService) Sign(msg []byte) ([]byte, error) {
Expand Down
154 changes: 112 additions & 42 deletions internal/peer/gossip/mcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/hyperledger/fabric/msp"
"github.com/hyperledger/fabric/msp/mgmt"
"github.com/hyperledger/fabric/protoutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -289,28 +290,68 @@ func TestVerifyBlock(t *testing.T) {
blockRaw2, msg2 := mockBlock(t, "D", 42, aliceSigner, nil)
policyManagerGetter.Managers["D"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msg2

// - Verify block
require.NoError(t, msgCryptoService.VerifyBlock([]byte("C"), 42, blockRaw))
// Wrong sequence number claimed
err = msgCryptoService.VerifyBlock([]byte("C"), 43, blockRaw)
require.Error(t, err)
require.Contains(t, err.Error(), "but actual seqNum inside block is")
delete(policyManagerGetter.Managers, "D")
nilPolMgrErr := msgCryptoService.VerifyBlock([]byte("D"), 42, blockRaw2)
require.Contains(t, nilPolMgrErr.Error(), "Could not acquire policy manager")
require.Error(t, nilPolMgrErr)
require.Error(t, msgCryptoService.VerifyBlock([]byte("A"), 42, blockRaw))
require.Error(t, msgCryptoService.VerifyBlock([]byte("B"), 42, blockRaw))

// - Prepare testing invalid block (wrong data has), Alice signs it.
blockRaw, msg = mockBlock(t, "C", 42, aliceSigner, []byte{0})
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msg

// - Verify block
require.Error(t, msgCryptoService.VerifyBlock([]byte("C"), 42, blockRaw))
t.Run("verify block", func(t *testing.T) {
require.NoError(t, msgCryptoService.VerifyBlock([]byte("C"), 42, blockRaw))
// Wrong sequence number claimed
err = msgCryptoService.VerifyBlock([]byte("C"), 43, blockRaw)
require.Error(t, err)
require.Contains(t, err.Error(), "but actual seqNum inside block is")
delete(policyManagerGetter.Managers, "D")
nilPolMgrErr := msgCryptoService.VerifyBlock([]byte("D"), 42, blockRaw2)
require.Contains(t, nilPolMgrErr.Error(), "Could not acquire policy manager")
require.Error(t, nilPolMgrErr)
require.Error(t, msgCryptoService.VerifyBlock([]byte("A"), 42, blockRaw))
require.Error(t, msgCryptoService.VerifyBlock([]byte("B"), 42, blockRaw))

// - Prepare testing invalid block (wrong data has), Alice signs it.
blockRawInvalid, msgInvalid := mockBlock(t, "C", 42, aliceSigner, []byte{0})
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msgInvalid
defer func() {
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msg
}()

// - Verify block
require.Error(t, msgCryptoService.VerifyBlock([]byte("C"), 42, blockRawInvalid))

// Check invalid args
require.Error(t, msgCryptoService.VerifyBlock([]byte("C"), 42, &common.Block{}))
})

// Check invalid args
require.Error(t, msgCryptoService.VerifyBlock([]byte("C"), 42, &common.Block{}))
t.Run("verify block attestation", func(t *testing.T) {
// An attestation is a signed block with block.Data = nil
attestation := blockRaw
attestation.Data = nil
attestation2 := blockRaw2
attestation2.Data = nil

assert.NoError(t, msgCryptoService.VerifyBlockAttestation("C", attestation))
delete(policyManagerGetter.Managers, "D")
nilPolMgrErr := msgCryptoService.VerifyBlockAttestation("D", attestation2)
assert.Contains(t, nilPolMgrErr.Error(), "Could not acquire policy manager")
assert.Error(t, nilPolMgrErr)
assert.Error(t, msgCryptoService.VerifyBlockAttestation("A", attestation))
assert.Error(t, msgCryptoService.VerifyBlockAttestation("B", attestation))

// - Prepare testing invalid attestation (wrong data has), Alice signs it.
// - Prepare testing invalid attestation (wrong data has), Alice signs it.
_, msgInvalid := mockBlock(t, "C", 42, aliceSigner, []byte{0})
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msgInvalid
defer func() {
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msg
}()

// - Verify attestation
assert.Error(t, msgCryptoService.VerifyBlockAttestation("C", attestation))

// Check invalid args
attestation.Header.DataHash = []byte{0, 1, 2, 3, 4}
assert.Error(t, msgCryptoService.VerifyBlockAttestation("C", attestation))
attestation.Metadata = nil
assert.Error(t, msgCryptoService.VerifyBlockAttestation("C", attestation))
attestation.Header = nil
assert.Error(t, msgCryptoService.VerifyBlockAttestation("C", attestation))
assert.Error(t, msgCryptoService.VerifyBlockAttestation("C", nil))
})
}

func TestVerifyBlockBFT(t *testing.T) {
Expand Down Expand Up @@ -368,28 +409,57 @@ func TestVerifyBlockBFT(t *testing.T) {
blockRaw2, msg2 := mockBlockBFT(t, "D", 42, aliceSigner, nil)
policyManagerGetter.Managers["D"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msg2

// - Verify block
require.NoError(t, msgCryptoService.VerifyBlock([]byte("C"), 42, blockRaw))
// Wrong sequence number claimed
err = msgCryptoService.VerifyBlock([]byte("C"), 43, blockRaw)
require.Error(t, err)
require.Contains(t, err.Error(), "but actual seqNum inside block is")
delete(policyManagerGetter.Managers, "D")
nilPolMgrErr := msgCryptoService.VerifyBlock([]byte("D"), 42, blockRaw2)
require.Contains(t, nilPolMgrErr.Error(), "Could not acquire policy manager")
require.Error(t, nilPolMgrErr)
require.Error(t, msgCryptoService.VerifyBlock([]byte("A"), 42, blockRaw))
require.Error(t, msgCryptoService.VerifyBlock([]byte("B"), 42, blockRaw))

// - Prepare testing invalid block (wrong data has), Alice signs it.
blockRaw, msg = mockBlockBFT(t, "C", 42, aliceSigner, []byte{0})
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msg

// - Verify block
require.Error(t, msgCryptoService.VerifyBlock([]byte("C"), 42, blockRaw))
t.Run("verify block", func(t *testing.T) {
// - Verify block
require.NoError(t, msgCryptoService.VerifyBlock([]byte("C"), 42, blockRaw))
// Wrong sequence number claimed
err = msgCryptoService.VerifyBlock([]byte("C"), 43, blockRaw)
require.Error(t, err)
require.Contains(t, err.Error(), "but actual seqNum inside block is")
delete(policyManagerGetter.Managers, "D")
nilPolMgrErr := msgCryptoService.VerifyBlock([]byte("D"), 42, blockRaw2)
require.Contains(t, nilPolMgrErr.Error(), "Could not acquire policy manager")
require.Error(t, nilPolMgrErr)
require.Error(t, msgCryptoService.VerifyBlock([]byte("A"), 42, blockRaw))
require.Error(t, msgCryptoService.VerifyBlock([]byte("B"), 42, blockRaw))

// - Prepare testing invalid block (wrong data has), Alice signs it.
blockRawInvalid, msgInvalid := mockBlockBFT(t, "C", 42, aliceSigner, []byte{0})
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msgInvalid
defer func() {
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msg
}()
// - Verify block
require.Error(t, msgCryptoService.VerifyBlock([]byte("C"), 42, blockRawInvalid))

// Check invalid args
require.Error(t, msgCryptoService.VerifyBlock([]byte("C"), 42, &common.Block{}))
})

// Check invalid args
require.Error(t, msgCryptoService.VerifyBlock([]byte("C"), 42, &common.Block{}))
t.Run("verify block attestation", func(t *testing.T) {
// An attestation is a signed block with block.Data = nil
attestation := blockRaw
attestation.Data = nil
attestation2 := blockRaw2
attestation2.Data = nil

// - Verify block
require.NoError(t, msgCryptoService.VerifyBlockAttestation("C", attestation))
nilPolMgrErr := msgCryptoService.VerifyBlockAttestation("D", attestation2)
require.Contains(t, nilPolMgrErr.Error(), "Could not acquire policy manager")
require.Error(t, nilPolMgrErr)
require.Error(t, msgCryptoService.VerifyBlockAttestation("A", attestation))
require.Error(t, msgCryptoService.VerifyBlockAttestation("B", attestation))

// - Prepare testing invalid block (has wrong data), Alice signs it.
_, msgInvalid := mockBlockBFT(t, "C", 42, aliceSigner, []byte{0})
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msgInvalid
defer func() {
policyManagerGetter.Managers["C"].(*mocks.ChannelPolicyManager).Policy.(*mocks.Policy).Deserializer.(*mocks.IdentityDeserializer).Msg = msg
}()
// - Verify block
require.Error(t, msgCryptoService.VerifyBlockAttestation("C", attestation))
})
}

func mockBlock(t *testing.T, channel string, seqNum uint64, localSigner *mocks.SignerSerializer, dataHash []byte) (*common.Block, []byte) {
Expand Down
5 changes: 5 additions & 0 deletions internal/pkg/peer/blocksprovider/blocksprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ type GossipServiceAdapter interface {
//go:generate counterfeiter -o fake/block_verifier.go --fake-name BlockVerifier . BlockVerifier
type BlockVerifier interface {
VerifyBlock(channelID gossipcommon.ChannelID, blockNum uint64, block *common.Block) error

// VerifyBlockAttestation does the same as VerifyBlock, except it assumes block.Data = nil. It therefore does not
// compute the block.Data.Hash() and compare it to the block.Header.DataHash. This is used when the orderer
// delivers a block with header & metadata only, as an attestation of block existence.
VerifyBlockAttestation(channelID string, block *common.Block) error
}

//go:generate counterfeiter -o fake/orderer_connection_source.go --fake-name OrdererConnectionSource . OrdererConnectionSource
Expand Down
Loading

0 comments on commit 7143cd2

Please sign in to comment.