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 }