github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/verification/combined_signer_v2_test.go (about)

     1  package verification
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/mock"
     7  	"github.com/stretchr/testify/require"
     8  
     9  	"github.com/koko1123/flow-go-1/consensus/hotstuff/mocks"
    10  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    11  	"github.com/koko1123/flow-go-1/consensus/hotstuff/signature"
    12  	"github.com/onflow/flow-go/crypto"
    13  	"github.com/koko1123/flow-go-1/model/flow"
    14  	"github.com/koko1123/flow-go-1/module/local"
    15  	modulemock "github.com/koko1123/flow-go-1/module/mock"
    16  	msig "github.com/koko1123/flow-go-1/module/signature"
    17  	"github.com/koko1123/flow-go-1/state/protocol"
    18  	storagemock "github.com/koko1123/flow-go-1/storage/mock"
    19  	"github.com/koko1123/flow-go-1/utils/unittest"
    20  )
    21  
    22  // Test that when DKG key is available for a view, a signed block can pass the validation
    23  // the sig include both staking sig and random beacon sig.
    24  func TestCombinedSignWithDKGKey(t *testing.T) {
    25  	identities := unittest.IdentityListFixture(4, unittest.WithRole(flow.RoleConsensus))
    26  
    27  	// prepare data
    28  	dkgKey := unittest.RandomBeaconPriv()
    29  	pk := dkgKey.PublicKey()
    30  	view := uint64(20)
    31  
    32  	fblock := unittest.BlockFixture()
    33  	fblock.Header.ProposerID = identities[0].NodeID
    34  	fblock.Header.View = view
    35  	block := model.BlockFromFlow(fblock.Header, 10)
    36  	signerID := fblock.Header.ProposerID
    37  
    38  	epochCounter := uint64(3)
    39  	epochLookup := &modulemock.EpochLookup{}
    40  	epochLookup.On("EpochForViewWithFallback", view).Return(epochCounter, nil)
    41  
    42  	keys := &storagemock.SafeBeaconKeys{}
    43  	// there is DKG key for this epoch
    44  	keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(dkgKey, true, nil)
    45  
    46  	beaconKeyStore := signature.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys)
    47  
    48  	stakingPriv := unittest.StakingPrivKeyFixture()
    49  	nodeID := unittest.IdentityFixture()
    50  	nodeID.NodeID = signerID
    51  	nodeID.StakingPubKey = stakingPriv.PublicKey()
    52  
    53  	me, err := local.New(nodeID, stakingPriv)
    54  	require.NoError(t, err)
    55  	signer := NewCombinedSigner(me, beaconKeyStore)
    56  
    57  	dkg := &mocks.DKG{}
    58  	dkg.On("KeyShare", signerID).Return(pk, nil)
    59  
    60  	committee := &mocks.Committee{}
    61  	committee.On("DKG", mock.Anything).Return(dkg, nil)
    62  
    63  	packer := signature.NewConsensusSigDataPacker(committee)
    64  	verifier := NewCombinedVerifier(committee, packer)
    65  
    66  	// check that a created proposal can be verified by a verifier
    67  	proposal, err := signer.CreateProposal(block)
    68  	require.NoError(t, err)
    69  
    70  	vote := proposal.ProposerVote()
    71  	err = verifier.VerifyVote(nodeID, vote.SigData, proposal.Block)
    72  	require.NoError(t, err)
    73  
    74  	// check that a created proposal's signature is a combined staking sig and random beacon sig
    75  	msg := MakeVoteMessage(block.View, block.BlockID)
    76  	stakingSig, err := stakingPriv.Sign(msg, msig.NewBLSHasher(msig.ConsensusVoteTag))
    77  	require.NoError(t, err)
    78  
    79  	beaconSig, err := dkgKey.Sign(msg, msig.NewBLSHasher(msig.RandomBeaconTag))
    80  	require.NoError(t, err)
    81  
    82  	expectedSig := msig.EncodeDoubleSig(stakingSig, beaconSig)
    83  	require.Equal(t, expectedSig, proposal.SigData)
    84  
    85  	// vote should be valid
    86  	vote, err = signer.CreateVote(block)
    87  	require.NoError(t, err)
    88  
    89  	err = verifier.VerifyVote(nodeID, vote.SigData, block)
    90  	require.NoError(t, err)
    91  
    92  	// vote on different block should be invalid
    93  	blockWrongID := *block
    94  	blockWrongID.BlockID[0]++
    95  	err = verifier.VerifyVote(nodeID, vote.SigData, &blockWrongID)
    96  	require.ErrorIs(t, err, model.ErrInvalidSignature)
    97  
    98  	// vote with a wrong view should be invalid
    99  	blockWrongView := *block
   100  	blockWrongView.View++
   101  	err = verifier.VerifyVote(nodeID, vote.SigData, &blockWrongView)
   102  	require.ErrorIs(t, err, model.ErrInvalidSignature)
   103  
   104  	// vote by different signer should be invalid
   105  	wrongVoter := identities[1]
   106  	wrongVoter.StakingPubKey = unittest.StakingPrivKeyFixture().PublicKey()
   107  	err = verifier.VerifyVote(wrongVoter, vote.SigData, block)
   108  	require.ErrorIs(t, err, model.ErrInvalidSignature)
   109  
   110  	// vote with changed signature should be invalid
   111  	brokenSig := append([]byte{}, vote.SigData...) // copy
   112  	brokenSig[4]++
   113  	err = verifier.VerifyVote(nodeID, brokenSig, block)
   114  	require.ErrorIs(t, err, model.ErrInvalidSignature)
   115  
   116  	// Vote from a node that is _not_ part of the Random Beacon committee should be rejected.
   117  	// Specifically, we expect that the verifier recognizes the `protocol.IdentityNotFoundError`
   118  	// as a sign of an invalid vote and wraps it into a `model.InvalidSignerError`.
   119  	*dkg = mocks.DKG{} // overwrite DKG mock with a new one
   120  	dkg.On("KeyShare", signerID).Return(nil, protocol.IdentityNotFoundError{NodeID: signerID})
   121  	err = verifier.VerifyVote(nodeID, vote.SigData, proposal.Block)
   122  	require.True(t, model.IsInvalidSignerError(err))
   123  }
   124  
   125  // Test that when DKG key is not available for a view, a signed block can pass the validation
   126  // the sig only include staking sig
   127  func TestCombinedSignWithNoDKGKey(t *testing.T) {
   128  	// prepare data
   129  	dkgKey := unittest.RandomBeaconPriv()
   130  	pk := dkgKey.PublicKey()
   131  	view := uint64(20)
   132  
   133  	fblock := unittest.BlockFixture()
   134  	fblock.Header.View = view
   135  	block := model.BlockFromFlow(fblock.Header, 10)
   136  	signerID := fblock.Header.ProposerID
   137  
   138  	epochCounter := uint64(3)
   139  	epochLookup := &modulemock.EpochLookup{}
   140  	epochLookup.On("EpochForViewWithFallback", view).Return(epochCounter, nil)
   141  
   142  	keys := &storagemock.SafeBeaconKeys{}
   143  	// there is no DKG key for this epoch
   144  	keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(nil, false, nil)
   145  
   146  	beaconKeyStore := signature.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys)
   147  
   148  	stakingPriv := unittest.StakingPrivKeyFixture()
   149  	nodeID := unittest.IdentityFixture()
   150  	nodeID.NodeID = signerID
   151  	nodeID.StakingPubKey = stakingPriv.PublicKey()
   152  
   153  	me, err := local.New(nodeID, stakingPriv)
   154  	require.NoError(t, err)
   155  	signer := NewCombinedSigner(me, beaconKeyStore)
   156  
   157  	dkg := &mocks.DKG{}
   158  	dkg.On("KeyShare", signerID).Return(pk, nil)
   159  
   160  	committee := &mocks.Committee{}
   161  	// even if the node failed DKG, and has no random beacon private key,
   162  	// but other nodes, who completed and succeeded DKG, have a public key
   163  	// for this failed node, which can be used to verify signature from
   164  	// this failed node.
   165  	committee.On("DKG", mock.Anything).Return(dkg, nil)
   166  
   167  	packer := signature.NewConsensusSigDataPacker(committee)
   168  	verifier := NewCombinedVerifier(committee, packer)
   169  
   170  	proposal, err := signer.CreateProposal(block)
   171  	require.NoError(t, err)
   172  
   173  	vote := proposal.ProposerVote()
   174  	err = verifier.VerifyVote(nodeID, vote.SigData, proposal.Block)
   175  	require.NoError(t, err)
   176  
   177  	// As the proposer does not have a Random Beacon Key, it should sign solely with its staking key.
   178  	// In this case, the SigData should be identical to the staking sig.
   179  	expectedStakingSig, err := stakingPriv.Sign(
   180  		MakeVoteMessage(block.View, block.BlockID),
   181  		msig.NewBLSHasher(msig.ConsensusVoteTag),
   182  	)
   183  	require.NoError(t, err)
   184  	require.Equal(t, expectedStakingSig, crypto.Signature(proposal.SigData))
   185  }
   186  
   187  // Test_VerifyQC_EmptySigners checks that Verifier returns an `model.InsufficientSignaturesError`
   188  // if `signers` input is empty or nil. This check should happen _before_ the Verifier calls into
   189  // any sub-components, because some (e.g. `crypto.AggregateBLSPublicKeys`) don't provide sufficient
   190  // sentinel errors to distinguish between internal problems and external byzantine inputs.
   191  func Test_VerifyQC_EmptySigners(t *testing.T) {
   192  	committee := &mocks.Committee{}
   193  	packer := signature.NewConsensusSigDataPacker(committee)
   194  	verifier := NewCombinedVerifier(committee, packer)
   195  
   196  	header := unittest.BlockHeaderFixture()
   197  	block := model.BlockFromFlow(header, header.View-1)
   198  	sigData := unittest.QCSigDataFixture()
   199  
   200  	err := verifier.VerifyQC([]*flow.Identity{}, sigData, block)
   201  	require.True(t, model.IsInsufficientSignaturesError(err))
   202  
   203  	err = verifier.VerifyQC(nil, sigData, block)
   204  	require.True(t, model.IsInsufficientSignaturesError(err))
   205  }