github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/signature/signer_indices_test.go (about)

     1  package signature_test
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/require"
     9  	"pgregory.net/rapid"
    10  
    11  	"github.com/onflow/flow-go/ledger/common/bitutils"
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/model/flow/filter"
    14  	"github.com/onflow/flow-go/model/flow/filter/id"
    15  	"github.com/onflow/flow-go/module/signature"
    16  	"github.com/onflow/flow-go/utils/unittest"
    17  )
    18  
    19  // TestEncodeDecodeIdentities verifies the two path of encoding -> decoding:
    20  //  1. Identifiers --encode--> Indices --decode--> Identifiers
    21  //  2. for the decoding step, we offer an optimized convenience function to directly
    22  //     decode to full identities: Indices --decode--> Identities
    23  func TestEncodeDecodeIdentities(t *testing.T) {
    24  	canonicalIdentities := unittest.IdentityListFixture(20).Sort(flow.Canonical[flow.Identity]).ToSkeleton()
    25  	canonicalIdentifiers := canonicalIdentities.NodeIDs()
    26  	for s := 0; s < 20; s++ {
    27  		for e := s; e < 20; e++ {
    28  			var signers = canonicalIdentities[s:e]
    29  
    30  			// encoding
    31  			indices, err := signature.EncodeSignersToIndices(canonicalIdentities.NodeIDs(), signers.NodeIDs())
    32  			require.NoError(t, err)
    33  
    34  			// decoding option 1: decode to Identifiers
    35  			decodedIDs, err := signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, indices)
    36  			require.NoError(t, err)
    37  			require.Equal(t, signers.NodeIDs(), decodedIDs)
    38  
    39  			// decoding option 2: decode to Identities
    40  			decodedIdentities, err := signature.DecodeSignerIndicesToIdentities(canonicalIdentities, indices)
    41  			require.NoError(t, err)
    42  			require.Equal(t, signers, decodedIdentities)
    43  		}
    44  	}
    45  }
    46  
    47  func TestEncodeDecodeIdentitiesFail(t *testing.T) {
    48  	canonicalIdentities := unittest.IdentityListFixture(20)
    49  	canonicalIdentifiers := canonicalIdentities.NodeIDs()
    50  	signers := canonicalIdentities[3:19]
    51  	validIndices, err := signature.EncodeSignersToIndices(canonicalIdentities.NodeIDs(), signers.NodeIDs())
    52  	require.NoError(t, err)
    53  
    54  	_, err = signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, validIndices)
    55  	require.NoError(t, err)
    56  
    57  	invalidSum := make([]byte, len(validIndices))
    58  	copy(invalidSum, validIndices)
    59  	if invalidSum[0] == byte(0) {
    60  		invalidSum[0] = byte(1)
    61  	} else {
    62  		invalidSum[0] = byte(0)
    63  	}
    64  	_, err = signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, invalidSum)
    65  	require.True(t, signature.IsInvalidSignerIndicesError(err), err)
    66  	require.ErrorIs(t, err, signature.ErrInvalidChecksum, err)
    67  
    68  	incompatibleLength := append(validIndices, byte(0))
    69  	_, err = signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, incompatibleLength)
    70  	require.True(t, signature.IsInvalidSignerIndicesError(err), err)
    71  	require.False(t, signature.IsInvalidSignerIndicesError(signature.NewInvalidSigTypesErrorf("sdf")))
    72  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, err)
    73  
    74  	illegallyPadded := make([]byte, len(validIndices))
    75  	copy(illegallyPadded, validIndices)
    76  	illegallyPadded[len(illegallyPadded)-1]++
    77  	_, err = signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, illegallyPadded)
    78  	require.True(t, signature.IsInvalidSignerIndicesError(err), err)
    79  	require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, err)
    80  }
    81  
    82  func TestEncodeIdentity(t *testing.T) {
    83  	only := unittest.IdentifierListFixture(1)
    84  	indices, err := signature.EncodeSignersToIndices(only, only)
    85  	require.NoError(t, err)
    86  	// byte(1,0,0,0,0,0,0,0)
    87  	require.Equal(t, []byte{byte(1 << 7)}, indices[signature.CheckSumLen:])
    88  }
    89  
    90  // TestEncodeFail verifies that an error is returned in case some signer is not part
    91  // of the set of canonicalIdentifiers
    92  func TestEncodeFail(t *testing.T) {
    93  	fullIdentities := unittest.IdentifierListFixture(20)
    94  	_, err := signature.EncodeSignersToIndices(fullIdentities[1:], fullIdentities[:10])
    95  	require.Error(t, err)
    96  }
    97  
    98  // Test_EncodeSignerToIndicesAndSigType uses fuzzy-testing framework Rapid to
    99  // test the method EncodeSignerToIndicesAndSigType:
   100  // * we generate a set of authorized signer: `committeeIdentities`
   101  // * part of this set is sampled as staking singers: `stakingSigners`
   102  // * another part of `committeeIdentities` is sampled as beacon singers: `beaconSigners`
   103  // * we encode the set and check that the results conform to the protocol specification
   104  func Test_EncodeSignerToIndicesAndSigType(t *testing.T) {
   105  	rapid.Check(t, func(t *rapid.T) {
   106  		// select total committee size, number of random beacon signers and number of staking signers
   107  		committeeSize := rapid.IntRange(1, 272).Draw(t, "committeeSize")
   108  		numStakingSigners := rapid.IntRange(0, committeeSize).Draw(t, "numStakingSigners")
   109  		numRandomBeaconSigners := rapid.IntRange(0, committeeSize-numStakingSigners).Draw(t, "numRandomBeaconSigners")
   110  
   111  		// create committee
   112  		committeeIdentities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical[flow.Identity])
   113  		committee := committeeIdentities.NodeIDs()
   114  		stakingSigners, beaconSigners := sampleSigners(t, committee, numStakingSigners, numRandomBeaconSigners)
   115  
   116  		// encode
   117  		prefixed, sigTypes, err := signature.EncodeSignerToIndicesAndSigType(committee, stakingSigners, beaconSigners)
   118  		require.NoError(t, err)
   119  
   120  		signerIndices, err := signature.CompareAndExtract(committeeIdentities.NodeIDs(), prefixed)
   121  		require.NoError(t, err)
   122  
   123  		// check verify signer indices
   124  		unorderedSigners := stakingSigners.Union(beaconSigners) // caution, the Union operation potentially changes the ordering
   125  		correctEncoding(t, signerIndices, committee, unorderedSigners)
   126  
   127  		// check sigTypes
   128  		canSigners := committeeIdentities.Filter(filter.HasNodeID[flow.Identity](unorderedSigners...)).NodeIDs() // generates list of signer IDs in canonical order
   129  		correctEncoding(t, sigTypes, canSigners, beaconSigners)
   130  	})
   131  }
   132  
   133  // Test_DecodeSigTypeToStakingAndBeaconSigners uses fuzzy-testing framework Rapid to
   134  // test the method DecodeSigTypeToStakingAndBeaconSigners:
   135  //   - we generate a set of authorized signer: `committeeIdentities`
   136  //   - part of this set is sampled as staking singers: `stakingSigners`
   137  //   - another part of `committeeIdentities` is sampled as beacon singers: `beaconSigners`
   138  //   - we encode the set and check that the results conform to the protocol specification
   139  //   - We encode the set using `EncodeSignerToIndicesAndSigType` (tested before) and then decode it.
   140  //     Thereby we should recover the original input. Caution, the order might be different,
   141  //     so we sort both sets.
   142  func Test_DecodeSigTypeToStakingAndBeaconSigners(t *testing.T) {
   143  	rapid.Check(t, func(t *rapid.T) {
   144  		// select total committee size, number of random beacon signers and number of staking signers
   145  		committeeSize := rapid.IntRange(1, 272).Draw(t, "committeeSize")
   146  		numStakingSigners := rapid.IntRange(0, committeeSize).Draw(t, "numStakingSigners")
   147  		numRandomBeaconSigners := rapid.IntRange(0, committeeSize-numStakingSigners).Draw(t, "numRandomBeaconSigners")
   148  
   149  		// create committee
   150  		committeeIdentities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).
   151  			Sort(flow.Canonical[flow.Identity])
   152  		committee := committeeIdentities.NodeIDs()
   153  		stakingSigners, beaconSigners := sampleSigners(t, committee, numStakingSigners, numRandomBeaconSigners)
   154  
   155  		// encode
   156  		signerIndices, sigTypes, err := signature.EncodeSignerToIndicesAndSigType(committee, stakingSigners, beaconSigners)
   157  		require.NoError(t, err)
   158  
   159  		// decode
   160  		decSignerIdentites, err := signature.DecodeSignerIndicesToIdentities(committeeIdentities.ToSkeleton(), signerIndices)
   161  		require.NoError(t, err)
   162  		decStakingSigners, decBeaconSigners, err := signature.DecodeSigTypeToStakingAndBeaconSigners(decSignerIdentites, sigTypes)
   163  		require.NoError(t, err)
   164  
   165  		// verify; note that there is a slightly different convention between Filter and the decoding logic:
   166  		// Filter returns nil for an empty list, while the decoding logic returns an instance of an empty slice
   167  		sigIdentities := committeeIdentities.Filter(
   168  			filter.Or(filter.HasNodeID[flow.Identity](stakingSigners...), filter.HasNodeID[flow.Identity](beaconSigners...))).ToSkeleton() // signer identities in canonical order
   169  		if len(stakingSigners)+len(decBeaconSigners) > 0 {
   170  			require.Equal(t, sigIdentities, decSignerIdentites)
   171  		}
   172  		if len(stakingSigners) == 0 {
   173  			require.Empty(t, decStakingSigners)
   174  		} else {
   175  			require.Equal(t, committeeIdentities.Filter(filter.HasNodeID[flow.Identity](stakingSigners...)).ToSkeleton(), decStakingSigners)
   176  		}
   177  		if len(decBeaconSigners) == 0 {
   178  			require.Empty(t, decBeaconSigners)
   179  		} else {
   180  			require.Equal(t, committeeIdentities.Filter(filter.HasNodeID[flow.Identity](beaconSigners...)).ToSkeleton(), decBeaconSigners)
   181  		}
   182  	})
   183  }
   184  
   185  func Test_ValidPaddingErrIncompatibleBitVectorLength(t *testing.T) {
   186  	var err error
   187  	// if bits is multiply of 8, then there is no padding needed, any sig type can be decoded.
   188  	signers := unittest.IdentityListFixture(16).ToSkeleton()
   189  
   190  	// 16 bits needs 2 bytes, provided 2 bytes
   191  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, unittest.RandomBytes(2))
   192  	require.NoError(t, err)
   193  
   194  	// 1 byte less
   195  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255)})
   196  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   197  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   198  
   199  	// 1 byte more
   200  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{})
   201  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   202  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   203  
   204  	// if bits is not multiply of 8, then padding is needed
   205  	signers = unittest.IdentityListFixture(15).ToSkeleton()
   206  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255), byte(254)})
   207  	require.NoError(t, err)
   208  
   209  	// 1 byte more
   210  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255), byte(255), byte(254)})
   211  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   212  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   213  
   214  	// 1 byte less
   215  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(254)})
   216  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   217  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   218  
   219  	// if bits is not multiply of 8,
   220  	// 1 byte more
   221  	signers = unittest.IdentityListFixture(0).ToSkeleton()
   222  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255)})
   223  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   224  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   225  
   226  	// 1 byte more
   227  	signers = unittest.IdentityListFixture(1).ToSkeleton()
   228  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(0), byte(0)})
   229  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   230  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   231  
   232  	// 1 byte less
   233  	signers = unittest.IdentityListFixture(7).ToSkeleton()
   234  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{})
   235  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   236  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   237  }
   238  
   239  func TestValidPaddingErrIllegallyPaddedBitVector(t *testing.T) {
   240  	var signers flow.IdentitySkeletonList
   241  	var err error
   242  	// if bits is multiply of 8, then there is no padding needed, any sig type can be decoded.
   243  	for count := 1; count < 8; count++ {
   244  		signers = unittest.IdentityListFixture(count).ToSkeleton()
   245  		_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255)}) // last bit should be 0, but 1
   246  		require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   247  		require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, "low-level error representing the failure should be ErrIllegallyPaddedBitVector")
   248  
   249  		_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(1)}) // last bit should be 0, but 1
   250  		require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   251  		require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, "low-level error representing the failure should be ErrIllegallyPaddedBitVector")
   252  	}
   253  
   254  	for count := 9; count < 16; count++ {
   255  		signers = unittest.IdentityListFixture(count).ToSkeleton()
   256  		_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255), byte(255)}) // last bit should be 0, but 1
   257  		require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   258  		require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, "low-level error representing the failure should be ErrIllegallyPaddedBitVector")
   259  
   260  		_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(1), byte(1)}) // last bit should be 0, but 1
   261  		require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   262  		require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, "low-level error representing the failure should be ErrIllegallyPaddedBitVector")
   263  	}
   264  }
   265  
   266  // Test_EncodeSignersToIndices uses fuzzy-testing framework Rapid to test the method EncodeSignersToIndices:
   267  // * we generate a set of authorized signer: `identities`
   268  // * part of this set is sampled as singers: `signers`
   269  // * we encode the set and check that the results conform to the protocol specification
   270  func Test_EncodeSignersToIndices(t *testing.T) {
   271  	rapid.Check(t, func(t *rapid.T) {
   272  		// select total committee size, number of random beacon signers and number of staking signers
   273  		committeeSize := rapid.IntRange(1, 272).Draw(t, "committeeSize")
   274  		numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners")
   275  
   276  		// create committee
   277  		identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical[flow.Identity])
   278  		committee := identities.NodeIDs()
   279  		signers, err := committee.Sample(uint(numSigners))
   280  		require.NoError(t, err)
   281  
   282  		// encode
   283  		prefixed, err := signature.EncodeSignersToIndices(committee, signers)
   284  		require.NoError(t, err)
   285  
   286  		signerIndices, err := signature.CompareAndExtract(committee, prefixed)
   287  		require.NoError(t, err)
   288  
   289  		// check verify signer indices
   290  		correctEncoding(t, signerIndices, committee, signers)
   291  	})
   292  }
   293  
   294  // Test_DecodeSignerIndicesToIdentifiers uses fuzzy-testing framework Rapid to test the method DecodeSignerIndicesToIdentifiers:
   295  //   - we generate a set of authorized signer: `identities`
   296  //   - part of this set is sampled as signers: `signers`
   297  //   - We encode the set using `EncodeSignersToIndices` (tested before) and then decode it.
   298  //     Thereby we should recover the original input. Caution, the order might be different,
   299  //     so we sort both sets.
   300  func Test_DecodeSignerIndicesToIdentifiers(t *testing.T) {
   301  	rapid.Check(t, func(t *rapid.T) {
   302  		// select total committee size, number of random beacon signers and number of staking signers
   303  		committeeSize := rapid.IntRange(1, 272).Draw(t, "committeeSize")
   304  		numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners")
   305  
   306  		// create committee
   307  		identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical[flow.Identity])
   308  		committee := identities.NodeIDs()
   309  		signers, err := committee.Sample(uint(numSigners))
   310  		require.NoError(t, err)
   311  		sort.Sort(signers)
   312  
   313  		// encode
   314  		signerIndices, err := signature.EncodeSignersToIndices(committee, signers)
   315  		require.NoError(t, err)
   316  
   317  		// decode and verify
   318  		decodedSigners, err := signature.DecodeSignerIndicesToIdentifiers(committee, signerIndices)
   319  		require.NoError(t, err)
   320  		sort.Sort(decodedSigners)
   321  		require.Equal(t, signers, decodedSigners)
   322  	})
   323  }
   324  
   325  // Test_DecodeSignerIndicesToIdentities uses fuzzy-testing framework Rapid to test the method DecodeSignerIndicesToIdentities:
   326  // * we generate a set of authorized signer: `identities`
   327  // * part of this set is sampled as singers: `signers`
   328  // * We encode the set using `EncodeSignersToIndices` (tested before) and then decode it.
   329  //   Thereby we should recover the original input. Caution, the order might be different,
   330  //   so we sort both sets.
   331  // Note: this is _almost_ the same test as `Test_DecodeSignerIndicesToIdentifiers`. However, in the other
   332  // test, we decode to node IDs; while in this test, we decode to full _Identities_.
   333  
   334  const UpperBoundCommitteeSize = 272
   335  
   336  func Test_DecodeSignerIndicesToIdentities(t *testing.T) {
   337  	rapid.Check(t, func(t *rapid.T) {
   338  		// select total committee size, number of random beacon signers and number of staking signers
   339  		committeeSize := rapid.IntRange(1, UpperBoundCommitteeSize).Draw(t, "committeeSize")
   340  		numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners")
   341  
   342  		// create committee
   343  		identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical[flow.Identity])
   344  		fullSigners, err := identities.Sample(uint(numSigners))
   345  		require.NoError(t, err)
   346  		signers := fullSigners.ToSkeleton()
   347  
   348  		// encode
   349  		signerIndices, err := signature.EncodeSignersToIndices(identities.NodeIDs(), signers.NodeIDs())
   350  		require.NoError(t, err)
   351  
   352  		// decode and verify
   353  		decodedSigners, err := signature.DecodeSignerIndicesToIdentities(identities.ToSkeleton(), signerIndices)
   354  		require.NoError(t, err)
   355  
   356  		require.Equal(t, signers.Sort(flow.Canonical[flow.IdentitySkeleton]), decodedSigners.Sort(flow.Canonical[flow.IdentitySkeleton]))
   357  	})
   358  }
   359  
   360  // sampleSigners takes `committee` and samples to _disjoint_ subsets
   361  // (`stakingSigners` and `randomBeaconSigners`) with the specified cardinality
   362  func sampleSigners(
   363  	t *rapid.T,
   364  	committee flow.IdentifierList,
   365  	numStakingSigners int,
   366  	numRandomBeaconSigners int,
   367  ) (stakingSigners flow.IdentifierList, randomBeaconSigners flow.IdentifierList) {
   368  	if numStakingSigners+numRandomBeaconSigners > len(committee) {
   369  		panic(fmt.Sprintf("Cannot sample %d nodes out of a committee is size %d", numStakingSigners+numRandomBeaconSigners, len(committee)))
   370  	}
   371  
   372  	var err error
   373  	stakingSigners, err = committee.Sample(uint(numStakingSigners))
   374  	require.NoError(t, err)
   375  	remaining := committee.Filter(id.Not(id.In(stakingSigners...)))
   376  	randomBeaconSigners, err = remaining.Sample(uint(numRandomBeaconSigners))
   377  	require.NoError(t, err)
   378  	return
   379  }
   380  
   381  // correctEncoding verifies that the given indices conform to the following specification:
   382  //   - indices is the _smallest_ possible byte slice that contains at least `len(canonicalIdentifiers)` number of _bits_
   383  //   - Let indices[i] denote the ith bit of `indices`. We verify that:
   384  //
   385  // .                            ┌ 1 if and only if canonicalIdentifiers[i] is in `subset`
   386  // .               indices[i] = └ 0 otherwise
   387  //
   388  // This function can be used to verify signer indices as well as signature type encoding
   389  func correctEncoding(t require.TestingT, indices []byte, canonicalIdentifiers flow.IdentifierList, subset flow.IdentifierList) {
   390  	// verify that indices has correct length
   391  	numberBits := 8 * len(indices)
   392  	require.True(t, numberBits >= len(canonicalIdentifiers), "signerIndices has too few bits")
   393  	require.True(t, numberBits-len(canonicalIdentifiers) < 8, fmt.Sprintf("signerIndices %v is padded with too many %v bits",
   394  		numberBits, len(canonicalIdentifiers)))
   395  
   396  	// convert canonicalIdentifiers to map Identifier -> index
   397  	m := make(map[flow.Identifier]int)
   398  	for i, id := range canonicalIdentifiers {
   399  		m[id] = i
   400  	}
   401  
   402  	// make sure that every member of the subset is represented by a 1 in `indices`
   403  	for _, id := range subset {
   404  		bitIndex := m[id]
   405  		require.True(t, bitutils.ReadBit(indices, bitIndex) == 1)
   406  		delete(m, id)
   407  	}
   408  
   409  	// as we delete all IDs in subset from m, the remaining ID in `m` should be represented by a 0 in `indices`
   410  	for id := range m {
   411  		bitIndex := m[id]
   412  		require.True(t, bitutils.ReadBit(indices, bitIndex) == 0)
   413  	}
   414  
   415  	// the padded bits should also all be 0:
   416  	for i := len(canonicalIdentifiers); i < 8*len(indices); i++ {
   417  		require.True(t, bitutils.ReadBit(indices, i) == 0)
   418  	}
   419  }