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