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  }