github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/warp/signature_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package warp
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"math"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/require"
    13  	"go.uber.org/mock/gomock"
    14  
    15  	"github.com/MetalBlockchain/metalgo/ids"
    16  	"github.com/MetalBlockchain/metalgo/snow/validators"
    17  	"github.com/MetalBlockchain/metalgo/utils"
    18  	"github.com/MetalBlockchain/metalgo/utils/constants"
    19  	"github.com/MetalBlockchain/metalgo/utils/crypto/bls"
    20  	"github.com/MetalBlockchain/metalgo/utils/set"
    21  )
    22  
    23  const pChainHeight uint64 = 1337
    24  
    25  var (
    26  	_ utils.Sortable[*testValidator] = (*testValidator)(nil)
    27  
    28  	errTest       = errors.New("non-nil error")
    29  	sourceChainID = ids.GenerateTestID()
    30  	subnetID      = ids.GenerateTestID()
    31  
    32  	testVdrs []*testValidator
    33  )
    34  
    35  type testValidator struct {
    36  	nodeID ids.NodeID
    37  	sk     *bls.SecretKey
    38  	vdr    *Validator
    39  }
    40  
    41  func (v *testValidator) Compare(o *testValidator) int {
    42  	return v.vdr.Compare(o.vdr)
    43  }
    44  
    45  func newTestValidator() *testValidator {
    46  	sk, err := bls.NewSecretKey()
    47  	if err != nil {
    48  		panic(err)
    49  	}
    50  
    51  	nodeID := ids.GenerateTestNodeID()
    52  	pk := bls.PublicFromSecretKey(sk)
    53  	return &testValidator{
    54  		nodeID: nodeID,
    55  		sk:     sk,
    56  		vdr: &Validator{
    57  			PublicKey:      pk,
    58  			PublicKeyBytes: bls.PublicKeyToUncompressedBytes(pk),
    59  			Weight:         3,
    60  			NodeIDs:        []ids.NodeID{nodeID},
    61  		},
    62  	}
    63  }
    64  
    65  func init() {
    66  	testVdrs = []*testValidator{
    67  		newTestValidator(),
    68  		newTestValidator(),
    69  		newTestValidator(),
    70  	}
    71  	utils.Sort(testVdrs)
    72  }
    73  
    74  func TestNumSigners(t *testing.T) {
    75  	tests := map[string]struct {
    76  		generateSignature func() *BitSetSignature
    77  		count             int
    78  		err               error
    79  	}{
    80  		"empty signers": {
    81  			generateSignature: func() *BitSetSignature {
    82  				return &BitSetSignature{}
    83  			},
    84  		},
    85  		"invalid signers": {
    86  			generateSignature: func() *BitSetSignature {
    87  				return &BitSetSignature{
    88  					Signers: make([]byte, 1),
    89  				}
    90  			},
    91  			err: ErrInvalidBitSet,
    92  		},
    93  		"no signers": {
    94  			generateSignature: func() *BitSetSignature {
    95  				signers := set.NewBits()
    96  				return &BitSetSignature{
    97  					Signers: signers.Bytes(),
    98  				}
    99  			},
   100  		},
   101  		"1 signer": {
   102  			generateSignature: func() *BitSetSignature {
   103  				signers := set.NewBits()
   104  				signers.Add(2)
   105  				return &BitSetSignature{
   106  					Signers: signers.Bytes(),
   107  				}
   108  			},
   109  			count: 1,
   110  		},
   111  		"multiple signers": {
   112  			generateSignature: func() *BitSetSignature {
   113  				signers := set.NewBits()
   114  				signers.Add(2)
   115  				signers.Add(11)
   116  				signers.Add(55)
   117  				signers.Add(93)
   118  				return &BitSetSignature{
   119  					Signers: signers.Bytes(),
   120  				}
   121  			},
   122  			count: 4,
   123  		},
   124  	}
   125  
   126  	for name, tt := range tests {
   127  		t.Run(name, func(t *testing.T) {
   128  			require := require.New(t)
   129  			sig := tt.generateSignature()
   130  			count, err := sig.NumSigners()
   131  			require.Equal(tt.count, count)
   132  			require.ErrorIs(err, tt.err)
   133  		})
   134  	}
   135  }
   136  
   137  func TestSignatureVerification(t *testing.T) {
   138  	vdrs := map[ids.NodeID]*validators.GetValidatorOutput{
   139  		testVdrs[0].nodeID: {
   140  			NodeID:    testVdrs[0].nodeID,
   141  			PublicKey: testVdrs[0].vdr.PublicKey,
   142  			Weight:    testVdrs[0].vdr.Weight,
   143  		},
   144  		testVdrs[1].nodeID: {
   145  			NodeID:    testVdrs[1].nodeID,
   146  			PublicKey: testVdrs[1].vdr.PublicKey,
   147  			Weight:    testVdrs[1].vdr.Weight,
   148  		},
   149  		testVdrs[2].nodeID: {
   150  			NodeID:    testVdrs[2].nodeID,
   151  			PublicKey: testVdrs[2].vdr.PublicKey,
   152  			Weight:    testVdrs[2].vdr.Weight,
   153  		},
   154  	}
   155  
   156  	tests := []struct {
   157  		name      string
   158  		networkID uint32
   159  		stateF    func(*gomock.Controller) validators.State
   160  		quorumNum uint64
   161  		quorumDen uint64
   162  		msgF      func(*require.Assertions) *Message
   163  		err       error
   164  	}{
   165  		{
   166  			name:      "can't get subnetID",
   167  			networkID: constants.UnitTestID,
   168  			stateF: func(ctrl *gomock.Controller) validators.State {
   169  				state := validators.NewMockState(ctrl)
   170  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, errTest)
   171  				return state
   172  			},
   173  			quorumNum: 1,
   174  			quorumDen: 2,
   175  			msgF: func(require *require.Assertions) *Message {
   176  				unsignedMsg, err := NewUnsignedMessage(
   177  					constants.UnitTestID,
   178  					sourceChainID,
   179  					nil,
   180  				)
   181  				require.NoError(err)
   182  
   183  				msg, err := NewMessage(
   184  					unsignedMsg,
   185  					&BitSetSignature{},
   186  				)
   187  				require.NoError(err)
   188  				return msg
   189  			},
   190  			err: errTest,
   191  		},
   192  		{
   193  			name:      "can't get validator set",
   194  			networkID: constants.UnitTestID,
   195  			stateF: func(ctrl *gomock.Controller) validators.State {
   196  				state := validators.NewMockState(ctrl)
   197  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   198  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(nil, errTest)
   199  				return state
   200  			},
   201  			quorumNum: 1,
   202  			quorumDen: 2,
   203  			msgF: func(require *require.Assertions) *Message {
   204  				unsignedMsg, err := NewUnsignedMessage(
   205  					constants.UnitTestID,
   206  					sourceChainID,
   207  					nil,
   208  				)
   209  				require.NoError(err)
   210  
   211  				msg, err := NewMessage(
   212  					unsignedMsg,
   213  					&BitSetSignature{},
   214  				)
   215  				require.NoError(err)
   216  				return msg
   217  			},
   218  			err: errTest,
   219  		},
   220  		{
   221  			name:      "weight overflow",
   222  			networkID: constants.UnitTestID,
   223  			stateF: func(ctrl *gomock.Controller) validators.State {
   224  				state := validators.NewMockState(ctrl)
   225  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   226  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(map[ids.NodeID]*validators.GetValidatorOutput{
   227  					testVdrs[0].nodeID: {
   228  						NodeID:    testVdrs[0].nodeID,
   229  						PublicKey: testVdrs[0].vdr.PublicKey,
   230  						Weight:    math.MaxUint64,
   231  					},
   232  					testVdrs[1].nodeID: {
   233  						NodeID:    testVdrs[1].nodeID,
   234  						PublicKey: testVdrs[1].vdr.PublicKey,
   235  						Weight:    math.MaxUint64,
   236  					},
   237  				}, nil)
   238  				return state
   239  			},
   240  			quorumNum: 1,
   241  			quorumDen: 2,
   242  			msgF: func(*require.Assertions) *Message {
   243  				return &Message{
   244  					UnsignedMessage: UnsignedMessage{
   245  						NetworkID:     constants.UnitTestID,
   246  						SourceChainID: sourceChainID,
   247  					},
   248  					Signature: &BitSetSignature{
   249  						Signers: make([]byte, 8),
   250  					},
   251  				}
   252  			},
   253  			err: ErrWeightOverflow,
   254  		},
   255  		{
   256  			name:      "invalid bit set index",
   257  			networkID: constants.UnitTestID,
   258  			stateF: func(ctrl *gomock.Controller) validators.State {
   259  				state := validators.NewMockState(ctrl)
   260  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   261  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   262  				return state
   263  			},
   264  			quorumNum: 1,
   265  			quorumDen: 2,
   266  			msgF: func(require *require.Assertions) *Message {
   267  				unsignedMsg, err := NewUnsignedMessage(
   268  					constants.UnitTestID,
   269  					sourceChainID,
   270  					[]byte{1, 2, 3},
   271  				)
   272  				require.NoError(err)
   273  
   274  				msg, err := NewMessage(
   275  					unsignedMsg,
   276  					&BitSetSignature{
   277  						Signers:   make([]byte, 1),
   278  						Signature: [bls.SignatureLen]byte{},
   279  					},
   280  				)
   281  				require.NoError(err)
   282  				return msg
   283  			},
   284  			err: ErrInvalidBitSet,
   285  		},
   286  		{
   287  			name:      "unknown index",
   288  			networkID: constants.UnitTestID,
   289  			stateF: func(ctrl *gomock.Controller) validators.State {
   290  				state := validators.NewMockState(ctrl)
   291  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   292  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   293  				return state
   294  			},
   295  			quorumNum: 1,
   296  			quorumDen: 2,
   297  			msgF: func(require *require.Assertions) *Message {
   298  				unsignedMsg, err := NewUnsignedMessage(
   299  					constants.UnitTestID,
   300  					sourceChainID,
   301  					[]byte{1, 2, 3},
   302  				)
   303  				require.NoError(err)
   304  
   305  				signers := set.NewBits()
   306  				signers.Add(3) // vdr oob
   307  
   308  				msg, err := NewMessage(
   309  					unsignedMsg,
   310  					&BitSetSignature{
   311  						Signers:   signers.Bytes(),
   312  						Signature: [bls.SignatureLen]byte{},
   313  					},
   314  				)
   315  				require.NoError(err)
   316  				return msg
   317  			},
   318  			err: ErrUnknownValidator,
   319  		},
   320  		{
   321  			name:      "insufficient weight",
   322  			networkID: constants.UnitTestID,
   323  			stateF: func(ctrl *gomock.Controller) validators.State {
   324  				state := validators.NewMockState(ctrl)
   325  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   326  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   327  				return state
   328  			},
   329  			quorumNum: 1,
   330  			quorumDen: 1,
   331  			msgF: func(require *require.Assertions) *Message {
   332  				unsignedMsg, err := NewUnsignedMessage(
   333  					constants.UnitTestID,
   334  					sourceChainID,
   335  					[]byte{1, 2, 3},
   336  				)
   337  				require.NoError(err)
   338  
   339  				// [signers] has weight from [vdr[0], vdr[1]],
   340  				// which is 6, which is less than 9
   341  				signers := set.NewBits()
   342  				signers.Add(0)
   343  				signers.Add(1)
   344  
   345  				unsignedBytes := unsignedMsg.Bytes()
   346  				vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes)
   347  				vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes)
   348  				aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr1Sig})
   349  				require.NoError(err)
   350  				aggSigBytes := [bls.SignatureLen]byte{}
   351  				copy(aggSigBytes[:], bls.SignatureToBytes(aggSig))
   352  
   353  				msg, err := NewMessage(
   354  					unsignedMsg,
   355  					&BitSetSignature{
   356  						Signers:   signers.Bytes(),
   357  						Signature: aggSigBytes,
   358  					},
   359  				)
   360  				require.NoError(err)
   361  				return msg
   362  			},
   363  			err: ErrInsufficientWeight,
   364  		},
   365  		{
   366  			name:      "can't parse sig",
   367  			networkID: constants.UnitTestID,
   368  			stateF: func(ctrl *gomock.Controller) validators.State {
   369  				state := validators.NewMockState(ctrl)
   370  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   371  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   372  				return state
   373  			},
   374  			quorumNum: 1,
   375  			quorumDen: 2,
   376  			msgF: func(require *require.Assertions) *Message {
   377  				unsignedMsg, err := NewUnsignedMessage(
   378  					constants.UnitTestID,
   379  					sourceChainID,
   380  					[]byte{1, 2, 3},
   381  				)
   382  				require.NoError(err)
   383  
   384  				signers := set.NewBits()
   385  				signers.Add(0)
   386  				signers.Add(1)
   387  
   388  				msg, err := NewMessage(
   389  					unsignedMsg,
   390  					&BitSetSignature{
   391  						Signers:   signers.Bytes(),
   392  						Signature: [bls.SignatureLen]byte{},
   393  					},
   394  				)
   395  				require.NoError(err)
   396  				return msg
   397  			},
   398  			err: ErrParseSignature,
   399  		},
   400  		{
   401  			name:      "no validators",
   402  			networkID: constants.UnitTestID,
   403  			stateF: func(ctrl *gomock.Controller) validators.State {
   404  				state := validators.NewMockState(ctrl)
   405  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   406  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(nil, nil)
   407  				return state
   408  			},
   409  			quorumNum: 1,
   410  			quorumDen: 2,
   411  			msgF: func(require *require.Assertions) *Message {
   412  				unsignedMsg, err := NewUnsignedMessage(
   413  					constants.UnitTestID,
   414  					sourceChainID,
   415  					[]byte{1, 2, 3},
   416  				)
   417  				require.NoError(err)
   418  
   419  				unsignedBytes := unsignedMsg.Bytes()
   420  				vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes)
   421  				aggSigBytes := [bls.SignatureLen]byte{}
   422  				copy(aggSigBytes[:], bls.SignatureToBytes(vdr0Sig))
   423  
   424  				msg, err := NewMessage(
   425  					unsignedMsg,
   426  					&BitSetSignature{
   427  						Signers:   nil,
   428  						Signature: aggSigBytes,
   429  					},
   430  				)
   431  				require.NoError(err)
   432  				return msg
   433  			},
   434  			err: bls.ErrNoPublicKeys,
   435  		},
   436  		{
   437  			name:      "invalid signature (substitute)",
   438  			networkID: constants.UnitTestID,
   439  			stateF: func(ctrl *gomock.Controller) validators.State {
   440  				state := validators.NewMockState(ctrl)
   441  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   442  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   443  				return state
   444  			},
   445  			quorumNum: 3,
   446  			quorumDen: 5,
   447  			msgF: func(require *require.Assertions) *Message {
   448  				unsignedMsg, err := NewUnsignedMessage(
   449  					constants.UnitTestID,
   450  					sourceChainID,
   451  					[]byte{1, 2, 3},
   452  				)
   453  				require.NoError(err)
   454  
   455  				signers := set.NewBits()
   456  				signers.Add(0)
   457  				signers.Add(1)
   458  
   459  				unsignedBytes := unsignedMsg.Bytes()
   460  				vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes)
   461  				// Give sig from vdr[2] even though the bit vector says it
   462  				// should be from vdr[1]
   463  				vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes)
   464  				aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr2Sig})
   465  				require.NoError(err)
   466  				aggSigBytes := [bls.SignatureLen]byte{}
   467  				copy(aggSigBytes[:], bls.SignatureToBytes(aggSig))
   468  
   469  				msg, err := NewMessage(
   470  					unsignedMsg,
   471  					&BitSetSignature{
   472  						Signers:   signers.Bytes(),
   473  						Signature: aggSigBytes,
   474  					},
   475  				)
   476  				require.NoError(err)
   477  				return msg
   478  			},
   479  			err: ErrInvalidSignature,
   480  		},
   481  		{
   482  			name:      "invalid signature (missing one)",
   483  			networkID: constants.UnitTestID,
   484  			stateF: func(ctrl *gomock.Controller) validators.State {
   485  				state := validators.NewMockState(ctrl)
   486  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   487  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   488  				return state
   489  			},
   490  			quorumNum: 3,
   491  			quorumDen: 5,
   492  			msgF: func(require *require.Assertions) *Message {
   493  				unsignedMsg, err := NewUnsignedMessage(
   494  					constants.UnitTestID,
   495  					sourceChainID,
   496  					[]byte{1, 2, 3},
   497  				)
   498  				require.NoError(err)
   499  
   500  				signers := set.NewBits()
   501  				signers.Add(0)
   502  				signers.Add(1)
   503  
   504  				unsignedBytes := unsignedMsg.Bytes()
   505  				vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes)
   506  				// Don't give the sig from vdr[1]
   507  				aggSigBytes := [bls.SignatureLen]byte{}
   508  				copy(aggSigBytes[:], bls.SignatureToBytes(vdr0Sig))
   509  
   510  				msg, err := NewMessage(
   511  					unsignedMsg,
   512  					&BitSetSignature{
   513  						Signers:   signers.Bytes(),
   514  						Signature: aggSigBytes,
   515  					},
   516  				)
   517  				require.NoError(err)
   518  				return msg
   519  			},
   520  			err: ErrInvalidSignature,
   521  		},
   522  		{
   523  			name:      "invalid signature (extra one)",
   524  			networkID: constants.UnitTestID,
   525  			stateF: func(ctrl *gomock.Controller) validators.State {
   526  				state := validators.NewMockState(ctrl)
   527  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   528  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   529  				return state
   530  			},
   531  			quorumNum: 3,
   532  			quorumDen: 5,
   533  			msgF: func(require *require.Assertions) *Message {
   534  				unsignedMsg, err := NewUnsignedMessage(
   535  					constants.UnitTestID,
   536  					sourceChainID,
   537  					[]byte{1, 2, 3},
   538  				)
   539  				require.NoError(err)
   540  
   541  				signers := set.NewBits()
   542  				signers.Add(0)
   543  				signers.Add(1)
   544  
   545  				unsignedBytes := unsignedMsg.Bytes()
   546  				vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes)
   547  				vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes)
   548  				// Give sig from vdr[2] even though the bit vector doesn't have
   549  				// it
   550  				vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes)
   551  				aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr1Sig, vdr2Sig})
   552  				require.NoError(err)
   553  				aggSigBytes := [bls.SignatureLen]byte{}
   554  				copy(aggSigBytes[:], bls.SignatureToBytes(aggSig))
   555  
   556  				msg, err := NewMessage(
   557  					unsignedMsg,
   558  					&BitSetSignature{
   559  						Signers:   signers.Bytes(),
   560  						Signature: aggSigBytes,
   561  					},
   562  				)
   563  				require.NoError(err)
   564  				return msg
   565  			},
   566  			err: ErrInvalidSignature,
   567  		},
   568  		{
   569  			name:      "valid signature",
   570  			networkID: constants.UnitTestID,
   571  			stateF: func(ctrl *gomock.Controller) validators.State {
   572  				state := validators.NewMockState(ctrl)
   573  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   574  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   575  				return state
   576  			},
   577  			quorumNum: 1,
   578  			quorumDen: 2,
   579  			msgF: func(require *require.Assertions) *Message {
   580  				unsignedMsg, err := NewUnsignedMessage(
   581  					constants.UnitTestID,
   582  					sourceChainID,
   583  					[]byte{1, 2, 3},
   584  				)
   585  				require.NoError(err)
   586  
   587  				// [signers] has weight from [vdr[1], vdr[2]],
   588  				// which is 6, which is greater than 4.5
   589  				signers := set.NewBits()
   590  				signers.Add(1)
   591  				signers.Add(2)
   592  
   593  				unsignedBytes := unsignedMsg.Bytes()
   594  				vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes)
   595  				vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes)
   596  				aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig})
   597  				require.NoError(err)
   598  				aggSigBytes := [bls.SignatureLen]byte{}
   599  				copy(aggSigBytes[:], bls.SignatureToBytes(aggSig))
   600  
   601  				msg, err := NewMessage(
   602  					unsignedMsg,
   603  					&BitSetSignature{
   604  						Signers:   signers.Bytes(),
   605  						Signature: aggSigBytes,
   606  					},
   607  				)
   608  				require.NoError(err)
   609  				return msg
   610  			},
   611  			err: nil,
   612  		},
   613  		{
   614  			name:      "valid signature (boundary)",
   615  			networkID: constants.UnitTestID,
   616  			stateF: func(ctrl *gomock.Controller) validators.State {
   617  				state := validators.NewMockState(ctrl)
   618  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   619  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(vdrs, nil)
   620  				return state
   621  			},
   622  			quorumNum: 2,
   623  			quorumDen: 3,
   624  			msgF: func(require *require.Assertions) *Message {
   625  				unsignedMsg, err := NewUnsignedMessage(
   626  					constants.UnitTestID,
   627  					sourceChainID,
   628  					[]byte{1, 2, 3},
   629  				)
   630  				require.NoError(err)
   631  
   632  				// [signers] has weight from [vdr[1], vdr[2]],
   633  				// which is 6, which meets the minimum 6
   634  				signers := set.NewBits()
   635  				signers.Add(1)
   636  				signers.Add(2)
   637  
   638  				unsignedBytes := unsignedMsg.Bytes()
   639  				vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes)
   640  				vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes)
   641  				aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig})
   642  				require.NoError(err)
   643  				aggSigBytes := [bls.SignatureLen]byte{}
   644  				copy(aggSigBytes[:], bls.SignatureToBytes(aggSig))
   645  
   646  				msg, err := NewMessage(
   647  					unsignedMsg,
   648  					&BitSetSignature{
   649  						Signers:   signers.Bytes(),
   650  						Signature: aggSigBytes,
   651  					},
   652  				)
   653  				require.NoError(err)
   654  				return msg
   655  			},
   656  			err: nil,
   657  		},
   658  		{
   659  			name:      "valid signature (missing key)",
   660  			networkID: constants.UnitTestID,
   661  			stateF: func(ctrl *gomock.Controller) validators.State {
   662  				state := validators.NewMockState(ctrl)
   663  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   664  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(map[ids.NodeID]*validators.GetValidatorOutput{
   665  					testVdrs[0].nodeID: {
   666  						NodeID:    testVdrs[0].nodeID,
   667  						PublicKey: nil,
   668  						Weight:    testVdrs[0].vdr.Weight,
   669  					},
   670  					testVdrs[1].nodeID: {
   671  						NodeID:    testVdrs[1].nodeID,
   672  						PublicKey: testVdrs[1].vdr.PublicKey,
   673  						Weight:    testVdrs[1].vdr.Weight,
   674  					},
   675  					testVdrs[2].nodeID: {
   676  						NodeID:    testVdrs[2].nodeID,
   677  						PublicKey: testVdrs[2].vdr.PublicKey,
   678  						Weight:    testVdrs[2].vdr.Weight,
   679  					},
   680  				}, nil)
   681  				return state
   682  			},
   683  			quorumNum: 1,
   684  			quorumDen: 3,
   685  			msgF: func(require *require.Assertions) *Message {
   686  				unsignedMsg, err := NewUnsignedMessage(
   687  					constants.UnitTestID,
   688  					sourceChainID,
   689  					[]byte{1, 2, 3},
   690  				)
   691  				require.NoError(err)
   692  
   693  				// [signers] has weight from [vdr2, vdr3],
   694  				// which is 6, which is greater than 3
   695  				signers := set.NewBits()
   696  				// Note: the bits are shifted because vdr[0]'s key was zeroed
   697  				signers.Add(0) // vdr[1]
   698  				signers.Add(1) // vdr[2]
   699  
   700  				unsignedBytes := unsignedMsg.Bytes()
   701  				vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes)
   702  				vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes)
   703  				aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig})
   704  				require.NoError(err)
   705  				aggSigBytes := [bls.SignatureLen]byte{}
   706  				copy(aggSigBytes[:], bls.SignatureToBytes(aggSig))
   707  
   708  				msg, err := NewMessage(
   709  					unsignedMsg,
   710  					&BitSetSignature{
   711  						Signers:   signers.Bytes(),
   712  						Signature: aggSigBytes,
   713  					},
   714  				)
   715  				require.NoError(err)
   716  				return msg
   717  			},
   718  			err: nil,
   719  		},
   720  		{
   721  			name:      "valid signature (duplicate key)",
   722  			networkID: constants.UnitTestID,
   723  			stateF: func(ctrl *gomock.Controller) validators.State {
   724  				state := validators.NewMockState(ctrl)
   725  				state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(subnetID, nil)
   726  				state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, subnetID).Return(map[ids.NodeID]*validators.GetValidatorOutput{
   727  					testVdrs[0].nodeID: {
   728  						NodeID:    testVdrs[0].nodeID,
   729  						PublicKey: nil,
   730  						Weight:    testVdrs[0].vdr.Weight,
   731  					},
   732  					testVdrs[1].nodeID: {
   733  						NodeID:    testVdrs[1].nodeID,
   734  						PublicKey: testVdrs[2].vdr.PublicKey,
   735  						Weight:    testVdrs[1].vdr.Weight,
   736  					},
   737  					testVdrs[2].nodeID: {
   738  						NodeID:    testVdrs[2].nodeID,
   739  						PublicKey: testVdrs[2].vdr.PublicKey,
   740  						Weight:    testVdrs[2].vdr.Weight,
   741  					},
   742  				}, nil)
   743  				return state
   744  			},
   745  			quorumNum: 2,
   746  			quorumDen: 3,
   747  			msgF: func(require *require.Assertions) *Message {
   748  				unsignedMsg, err := NewUnsignedMessage(
   749  					constants.UnitTestID,
   750  					sourceChainID,
   751  					[]byte{1, 2, 3},
   752  				)
   753  				require.NoError(err)
   754  
   755  				// [signers] has weight from [vdr2, vdr3],
   756  				// which is 6, which meets the minimum 6
   757  				signers := set.NewBits()
   758  				// Note: the bits are shifted because vdr[0]'s key was zeroed
   759  				// Note: vdr[1] and vdr[2] were combined because of a shared pk
   760  				signers.Add(0) // vdr[1] + vdr[2]
   761  
   762  				unsignedBytes := unsignedMsg.Bytes()
   763  				// Because vdr[1] and vdr[2] share a key, only one of them sign.
   764  				vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes)
   765  				aggSigBytes := [bls.SignatureLen]byte{}
   766  				copy(aggSigBytes[:], bls.SignatureToBytes(vdr2Sig))
   767  
   768  				msg, err := NewMessage(
   769  					unsignedMsg,
   770  					&BitSetSignature{
   771  						Signers:   signers.Bytes(),
   772  						Signature: aggSigBytes,
   773  					},
   774  				)
   775  				require.NoError(err)
   776  				return msg
   777  			},
   778  			err: nil,
   779  		},
   780  		{
   781  			name:      "incorrect networkID",
   782  			networkID: constants.UnitTestID,
   783  			stateF: func(ctrl *gomock.Controller) validators.State {
   784  				state := validators.NewMockState(ctrl)
   785  				return state
   786  			},
   787  			quorumNum: 1,
   788  			quorumDen: 2,
   789  			msgF: func(require *require.Assertions) *Message {
   790  				unsignedMsg, err := NewUnsignedMessage(
   791  					constants.UnitTestID+1,
   792  					sourceChainID,
   793  					[]byte{1, 2, 3},
   794  				)
   795  				require.NoError(err)
   796  
   797  				// [signers] has weight from [vdr[1], vdr[2]],
   798  				// which is 6, which is greater than 4.5
   799  				signers := set.NewBits()
   800  				signers.Add(1)
   801  				signers.Add(2)
   802  
   803  				unsignedBytes := unsignedMsg.Bytes()
   804  				vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes)
   805  				vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes)
   806  				aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig})
   807  				require.NoError(err)
   808  				aggSigBytes := [bls.SignatureLen]byte{}
   809  				copy(aggSigBytes[:], bls.SignatureToBytes(aggSig))
   810  
   811  				msg, err := NewMessage(
   812  					unsignedMsg,
   813  					&BitSetSignature{
   814  						Signers:   signers.Bytes(),
   815  						Signature: aggSigBytes,
   816  					},
   817  				)
   818  				require.NoError(err)
   819  				return msg
   820  			},
   821  			err: ErrWrongNetworkID,
   822  		},
   823  	}
   824  
   825  	for _, tt := range tests {
   826  		t.Run(tt.name, func(t *testing.T) {
   827  			require := require.New(t)
   828  			ctrl := gomock.NewController(t)
   829  
   830  			msg := tt.msgF(require)
   831  			pChainState := tt.stateF(ctrl)
   832  
   833  			err := msg.Signature.Verify(
   834  				context.Background(),
   835  				&msg.UnsignedMessage,
   836  				tt.networkID,
   837  				pChainState,
   838  				pChainHeight,
   839  				tt.quorumNum,
   840  				tt.quorumDen,
   841  			)
   842  			require.ErrorIs(err, tt.err)
   843  		})
   844  	}
   845  }