github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/verification/combined_signer_v3_test.go (about) 1 package verification 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/mock" 8 "github.com/stretchr/testify/require" 9 10 "github.com/koko1123/flow-go-1/consensus/hotstuff" 11 "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 12 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 13 "github.com/koko1123/flow-go-1/consensus/hotstuff/signature" 14 "github.com/koko1123/flow-go-1/model/encoding" 15 "github.com/koko1123/flow-go-1/model/flow" 16 "github.com/koko1123/flow-go-1/module/local" 17 modulemock "github.com/koko1123/flow-go-1/module/mock" 18 msig "github.com/koko1123/flow-go-1/module/signature" 19 "github.com/koko1123/flow-go-1/state/protocol" 20 storagemock "github.com/koko1123/flow-go-1/storage/mock" 21 "github.com/koko1123/flow-go-1/utils/unittest" 22 "github.com/onflow/flow-go/crypto" 23 ) 24 25 // Test that when DKG key is available for a view, a signed block can pass the validation 26 // the sig is a random beacon sig. 27 func TestCombinedSignWithDKGKeyV3(t *testing.T) { 28 // prepare data 29 dkgKey := unittest.RandomBeaconPriv() 30 pk := dkgKey.PublicKey() 31 view := uint64(20) 32 33 fblock := unittest.BlockFixture() 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 := NewCombinedSignerV3(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 := NewCombinedVerifierV3(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 77 beaconSig, err := dkgKey.Sign(msg, msig.NewBLSHasher(msig.RandomBeaconTag)) 78 require.NoError(t, err) 79 80 expectedSig := msig.EncodeSingleSig(encoding.SigTypeRandomBeacon, beaconSig) 81 require.Equal(t, expectedSig, proposal.SigData) 82 83 // Vote from a node that is _not_ part of the Random Beacon committee should be rejected. 84 // Specifically, we expect that the verifier recognizes the `protocol.IdentityNotFoundError` 85 // as a sign of an invalid vote and wraps it into a `model.InvalidSignerError`. 86 *dkg = mocks.DKG{} // overwrite DKG mock with a new one 87 dkg.On("KeyShare", signerID).Return(nil, protocol.IdentityNotFoundError{NodeID: signerID}) 88 err = verifier.VerifyVote(nodeID, vote.SigData, proposal.Block) 89 require.True(t, model.IsInvalidSignerError(err)) 90 } 91 92 // Test that when DKG key is not available for a view, a signed block can pass the validation 93 // the sig is a staking sig 94 func TestCombinedSignWithNoDKGKeyV3(t *testing.T) { 95 // prepare data 96 dkgKey := unittest.RandomBeaconPriv() 97 pk := dkgKey.PublicKey() 98 view := uint64(20) 99 100 fblock := unittest.BlockFixture() 101 fblock.Header.View = view 102 block := model.BlockFromFlow(fblock.Header, 10) 103 signerID := fblock.Header.ProposerID 104 105 epochCounter := uint64(3) 106 epochLookup := &modulemock.EpochLookup{} 107 epochLookup.On("EpochForViewWithFallback", view).Return(epochCounter, nil) 108 109 keys := &storagemock.SafeBeaconKeys{} 110 // there is no DKG key for this epoch 111 keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(nil, false, nil) 112 113 beaconKeyStore := signature.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys) 114 115 stakingPriv := unittest.StakingPrivKeyFixture() 116 nodeID := unittest.IdentityFixture() 117 nodeID.NodeID = signerID 118 nodeID.StakingPubKey = stakingPriv.PublicKey() 119 120 me, err := local.New(nodeID, stakingPriv) 121 require.NoError(t, err) 122 signer := NewCombinedSignerV3(me, beaconKeyStore) 123 124 dkg := &mocks.DKG{} 125 dkg.On("KeyShare", signerID).Return(pk, nil) 126 127 committee := &mocks.Committee{} 128 // even if the node failed DKG, and has no random beacon private key, 129 // but other nodes, who completed and succeeded DKG, have a public key 130 // for this failed node, which can be used to verify signature from 131 // this failed node. 132 committee.On("DKG", mock.Anything).Return(dkg, nil) 133 134 packer := signature.NewConsensusSigDataPacker(committee) 135 verifier := NewCombinedVerifierV3(committee, packer) 136 137 proposal, err := signer.CreateProposal(block) 138 require.NoError(t, err) 139 140 vote := proposal.ProposerVote() 141 err = verifier.VerifyVote(nodeID, vote.SigData, proposal.Block) 142 require.NoError(t, err) 143 144 // check that a created proposal's signature is a combined staking sig and random beacon sig 145 msg := MakeVoteMessage(block.View, block.BlockID) 146 stakingSig, err := stakingPriv.Sign(msg, msig.NewBLSHasher(msig.ConsensusVoteTag)) 147 require.NoError(t, err) 148 149 expectedSig := msig.EncodeSingleSig(encoding.SigTypeStaking, stakingSig) 150 151 // check the signature only has staking sig 152 require.Equal(t, expectedSig, proposal.SigData) 153 } 154 155 // Test_VerifyQC checks that a QC where either signer list is empty is rejected as invalid 156 func Test_VerifyQCV3(t *testing.T) { 157 header := unittest.BlockHeaderFixture() 158 block := model.BlockFromFlow(header, header.View-1) 159 msg := MakeVoteMessage(block.View, block.BlockID) 160 161 // generate some BLS key as a stub of the random beacon group key and use it to generate a reconstructed beacon sig 162 privGroupKey, beaconSig := generateSignature(t, msg, msig.RandomBeaconTag) 163 dkg := &mocks.DKG{} 164 dkg.On("GroupKey").Return(privGroupKey.PublicKey(), nil) 165 dkg.On("Size").Return(uint(20)) 166 committee := &mocks.Committee{} 167 committee.On("DKG", mock.Anything).Return(dkg, nil) 168 169 // generate 17 BLS keys as stubs for staking keys and use them to generate an aggregated staking sig 170 privStakingKeys, aggStakingSig := generateAggregatedSignature(t, 17, msg, msig.ConsensusVoteTag) 171 // generate 11 BLS keys as stubs for individual random beacon key shares and use them to generate an aggregated rand beacon sig 172 privRbKeyShares, aggRbSig := generateAggregatedSignature(t, 11, msg, msig.RandomBeaconTag) 173 174 stakingSigners := generateIdentitiesForPrivateKeys(t, privStakingKeys) 175 rbSigners := generateIdentitiesForPrivateKeys(t, privRbKeyShares) 176 registerPublicRbKeys(t, dkg, rbSigners.NodeIDs(), privRbKeyShares) 177 allSigners := append(append(flow.IdentityList{}, stakingSigners...), rbSigners...) 178 179 packedSigData := unittest.RandomBytes(1021) 180 unpackedSigData := hotstuff.BlockSignatureData{ 181 StakingSigners: stakingSigners.NodeIDs(), 182 AggregatedStakingSig: aggStakingSig, 183 RandomBeaconSigners: rbSigners.NodeIDs(), 184 AggregatedRandomBeaconSig: aggRbSig, 185 ReconstructedRandomBeaconSig: beaconSig, 186 } 187 188 // first, we check that our testing setup works for a correct QC 189 t.Run("valid QC", func(t *testing.T) { 190 packer := &mocks.Packer{} 191 packer.On("Unpack", mock.Anything, packedSigData).Return(&unpackedSigData, nil) 192 193 verifier := NewCombinedVerifierV3(committee, packer) 194 err := verifier.VerifyQC(allSigners, packedSigData, block) 195 require.NoError(t, err) 196 }) 197 198 // Here, we test correct verification of a QC, where all replicas signed with their 199 // random beacon keys. This is optimal happy path. 200 // * empty list of staking signers 201 // * _no_ aggregated staking sig in QC 202 // The Verifier should accept such QC 203 t.Run("all replicas signed with random beacon keys", func(t *testing.T) { 204 sd := unpackedSigData // copy correct QC 205 sd.StakingSigners = []flow.Identifier{} 206 sd.AggregatedStakingSig = []byte{} 207 208 packer := &mocks.Packer{} 209 packer.On("Unpack", mock.Anything, packedSigData).Return(&sd, nil) 210 verifier := NewCombinedVerifierV3(committee, packer) 211 err := verifier.VerifyQC(allSigners, packedSigData, block) 212 require.NoError(t, err) 213 }) 214 215 // Modify the correct QC: 216 // * empty list of staking signers 217 // * but an aggregated staking sig is given 218 // The Verifier should recognize this as an invalid QC. 219 t.Run("empty staking signers but aggregated staking sig in QC", func(t *testing.T) { 220 sd := unpackedSigData // copy correct QC 221 sd.StakingSigners = []flow.Identifier{} 222 223 packer := &mocks.Packer{} 224 packer.On("Unpack", mock.Anything, packedSigData).Return(&sd, nil) 225 verifier := NewCombinedVerifierV3(committee, packer) 226 err := verifier.VerifyQC(allSigners, packedSigData, block) 227 require.True(t, model.IsInvalidFormatError(err)) 228 }) 229 230 // Modify the correct QC: empty list of random beacon signers. 231 // The Verifier should recognize this as an invalid QC 232 t.Run("empty random beacon signers", func(t *testing.T) { 233 sd := unpackedSigData // copy correct QC 234 sd.RandomBeaconSigners = []flow.Identifier{} 235 236 packer := &mocks.Packer{} 237 packer.On("Unpack", mock.Anything, packedSigData).Return(&sd, nil) 238 verifier := NewCombinedVerifierV3(committee, packer) 239 err := verifier.VerifyQC(allSigners, packedSigData, block) 240 require.True(t, model.IsInvalidFormatError(err)) 241 }) 242 243 // Modify the correct QC: too few random beacon signers. 244 // The Verifier should recognize this as an invalid QC 245 t.Run("too few random beacon signers", func(t *testing.T) { 246 // In total, we have 20 DKG participants, i.e. we require at least 10 random 247 // beacon sig shares. But we only supply 5 aggregated key shares. 248 sd := unpackedSigData // copy correct QC 249 sd.RandomBeaconSigners = rbSigners[:5].NodeIDs() 250 sd.AggregatedRandomBeaconSig = aggregatedSignature(t, privRbKeyShares[:5], msg, msig.RandomBeaconTag) 251 252 packer := &mocks.Packer{} 253 packer.On("Unpack", mock.Anything, packedSigData).Return(&sd, nil) 254 verifier := NewCombinedVerifierV3(committee, packer) 255 err := verifier.VerifyQC(allSigners, packedSigData, block) 256 require.True(t, model.IsInvalidFormatError(err)) 257 }) 258 259 } 260 261 // Test_VerifyQC_EmptySignersV3 checks that Verifier returns an `model.InsufficientSignaturesError` 262 // if `signers` input is empty or nil. This check should happen _before_ the Verifier calls into 263 // any sub-components, because some (e.g. `crypto.AggregateBLSPublicKeys`) don't provide sufficient 264 // sentinel errors to distinguish between internal problems and external byzantine inputs. 265 func Test_VerifyQC_EmptySignersV3(t *testing.T) { 266 committee := &mocks.Committee{} 267 packer := signature.NewConsensusSigDataPacker(committee) 268 verifier := NewCombinedVerifier(committee, packer) 269 270 header := unittest.BlockHeaderFixture() 271 block := model.BlockFromFlow(header, header.View-1) 272 sigData := unittest.QCSigDataFixture() 273 274 err := verifier.VerifyQC([]*flow.Identity{}, sigData, block) 275 require.True(t, model.IsInsufficientSignaturesError(err)) 276 277 err = verifier.VerifyQC(nil, sigData, block) 278 require.True(t, model.IsInsufficientSignaturesError(err)) 279 } 280 281 func generateIdentitiesForPrivateKeys(t *testing.T, pivKeys []crypto.PrivateKey) flow.IdentityList { 282 ids := make([]*flow.Identity, 0, len(pivKeys)) 283 for _, k := range pivKeys { 284 id := unittest.IdentityFixture( 285 unittest.WithRole(flow.RoleConsensus), 286 unittest.WithStakingPubKey(k.PublicKey()), 287 ) 288 ids = append(ids, id) 289 } 290 return ids 291 } 292 293 func registerPublicRbKeys(t *testing.T, dkg *mocks.DKG, signerIDs []flow.Identifier, pivKeys []crypto.PrivateKey) { 294 assert.Equal(t, len(signerIDs), len(pivKeys), "one signer ID per key expected") 295 for k, id := range signerIDs { 296 dkg.On("KeyShare", id).Return(pivKeys[k].PublicKey(), nil) 297 } 298 } 299 300 // generateAggregatedSignature generates `n` private BLS keys, signs `msg` which each key, 301 // and aggregates the resulting sigs. Returns private keys and aggregated sig. 302 func generateAggregatedSignature(t *testing.T, n int, msg []byte, tag string) ([]crypto.PrivateKey, crypto.Signature) { 303 sigs := make([]crypto.Signature, 0, n) 304 privs := make([]crypto.PrivateKey, 0, n) 305 for ; n > 0; n-- { 306 priv, sig := generateSignature(t, msg, tag) 307 sigs = append(sigs, sig) 308 privs = append(privs, priv) 309 } 310 agg, err := crypto.AggregateBLSSignatures(sigs) 311 require.NoError(t, err) 312 return privs, agg 313 } 314 315 // generateSignature creates a single private BLS 12-381 key, signs the provided `message` with 316 // using domain separation `tag` and return the private key and signature. 317 func generateSignature(t *testing.T, message []byte, tag string) (crypto.PrivateKey, crypto.Signature) { 318 priv := unittest.PrivateKeyFixture(crypto.BLSBLS12381, crypto.KeyGenSeedMinLenBLSBLS12381) 319 sig, err := priv.Sign(message, msig.NewBLSHasher(tag)) 320 require.NoError(t, err) 321 return priv, sig 322 } 323 324 func aggregatedSignature(t *testing.T, pivKeys []crypto.PrivateKey, message []byte, tag string) crypto.Signature { 325 hasher := msig.NewBLSHasher(tag) 326 sigs := make([]crypto.Signature, 0, len(pivKeys)) 327 for _, k := range pivKeys { 328 sig, err := k.Sign(message, hasher) 329 require.NoError(t, err) 330 sigs = append(sigs, sig) 331 } 332 agg, err := crypto.AggregateBLSSignatures(sigs) 333 require.NoError(t, err) 334 return agg 335 }