github.com/onflow/flow-go@v0.33.17/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)
    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)
   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(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)).Sort(flow.Canonical)
   151  		committee := committeeIdentities.NodeIDs()
   152  		stakingSigners, beaconSigners := sampleSigners(t, committee, numStakingSigners, numRandomBeaconSigners)
   153  
   154  		// encode
   155  		signerIndices, sigTypes, err := signature.EncodeSignerToIndicesAndSigType(committee, stakingSigners, beaconSigners)
   156  		require.NoError(t, err)
   157  
   158  		// decode
   159  		decSignerIdentites, err := signature.DecodeSignerIndicesToIdentities(committeeIdentities, signerIndices)
   160  		require.NoError(t, err)
   161  		decStakingSigners, decBeaconSigners, err := signature.DecodeSigTypeToStakingAndBeaconSigners(decSignerIdentites, sigTypes)
   162  		require.NoError(t, err)
   163  
   164  		// verify; note that there is a slightly different convention between Filter and the decoding logic:
   165  		// Filter returns nil for an empty list, while the decoding logic returns an instance of an empty slice
   166  		sigIdentities := committeeIdentities.Filter(filter.Or(filter.HasNodeID(stakingSigners...), filter.HasNodeID(beaconSigners...))) // signer identities in canonical order
   167  		if len(stakingSigners)+len(decBeaconSigners) > 0 {
   168  			require.Equal(t, sigIdentities, decSignerIdentites)
   169  		}
   170  		if len(stakingSigners) == 0 {
   171  			require.Empty(t, decStakingSigners)
   172  		} else {
   173  			require.Equal(t, committeeIdentities.Filter(filter.HasNodeID(stakingSigners...)), decStakingSigners)
   174  		}
   175  		if len(decBeaconSigners) == 0 {
   176  			require.Empty(t, decBeaconSigners)
   177  		} else {
   178  			require.Equal(t, committeeIdentities.Filter(filter.HasNodeID(beaconSigners...)), decBeaconSigners)
   179  		}
   180  	})
   181  }
   182  
   183  func Test_ValidPaddingErrIncompatibleBitVectorLength(t *testing.T) {
   184  	var signers flow.IdentityList
   185  	var err error
   186  	// if bits is multiply of 8, then there is no padding needed, any sig type can be decoded.
   187  	signers = unittest.IdentityListFixture(16)
   188  
   189  	// 16 bits needs 2 bytes, provided 2 bytes
   190  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, unittest.RandomBytes(2))
   191  	require.NoError(t, err)
   192  
   193  	// 1 byte less
   194  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255)})
   195  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   196  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   197  
   198  	// 1 byte more
   199  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{})
   200  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   201  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   202  
   203  	// if bits is not multiply of 8, then padding is needed
   204  	signers = unittest.IdentityListFixture(15)
   205  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255), byte(254)})
   206  	require.NoError(t, err)
   207  
   208  	// 1 byte more
   209  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255), byte(255), byte(254)})
   210  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   211  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   212  
   213  	// 1 byte less
   214  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(254)})
   215  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   216  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   217  
   218  	// if bits is not multiply of 8,
   219  	// 1 byte more
   220  	signers = unittest.IdentityListFixture(0)
   221  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255)})
   222  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   223  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   224  
   225  	// 1 byte more
   226  	signers = unittest.IdentityListFixture(1)
   227  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(0), byte(0)})
   228  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   229  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   230  
   231  	// 1 byte less
   232  	signers = unittest.IdentityListFixture(7)
   233  	_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{})
   234  	require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   235  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, "low-level error representing the failure should be ErrIncompatibleBitVectorLength")
   236  }
   237  
   238  func TestValidPaddingErrIllegallyPaddedBitVector(t *testing.T) {
   239  	var signers flow.IdentityList
   240  	var err error
   241  	// if bits is multiply of 8, then there is no padding needed, any sig type can be decoded.
   242  	for count := 1; count < 8; count++ {
   243  		signers = unittest.IdentityListFixture(count)
   244  		_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255)}) // last bit should be 0, but 1
   245  		require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   246  		require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, "low-level error representing the failure should be ErrIllegallyPaddedBitVector")
   247  
   248  		_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(1)}) // last bit should be 0, but 1
   249  		require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   250  		require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, "low-level error representing the failure should be ErrIllegallyPaddedBitVector")
   251  	}
   252  
   253  	for count := 9; count < 16; count++ {
   254  		signers = unittest.IdentityListFixture(count)
   255  		_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(255), byte(255)}) // last bit should be 0, but 1
   256  		require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   257  		require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, "low-level error representing the failure should be ErrIllegallyPaddedBitVector")
   258  
   259  		_, _, err = signature.DecodeSigTypeToStakingAndBeaconSigners(signers, []byte{byte(1), byte(1)}) // last bit should be 0, but 1
   260  		require.True(t, signature.IsInvalidSigTypesError(err), "API-level error should be InvalidSigTypesError")
   261  		require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, "low-level error representing the failure should be ErrIllegallyPaddedBitVector")
   262  	}
   263  }
   264  
   265  // Test_EncodeSignersToIndices uses fuzzy-testing framework Rapid to test the method EncodeSignersToIndices:
   266  // * we generate a set of authorized signer: `identities`
   267  // * part of this set is sampled as singers: `signers`
   268  // * we encode the set and check that the results conform to the protocol specification
   269  func Test_EncodeSignersToIndices(t *testing.T) {
   270  	rapid.Check(t, func(t *rapid.T) {
   271  		// select total committee size, number of random beacon signers and number of staking signers
   272  		committeeSize := rapid.IntRange(1, 272).Draw(t, "committeeSize")
   273  		numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners")
   274  
   275  		// create committee
   276  		identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical)
   277  		committee := identities.NodeIDs()
   278  		signers, err := committee.Sample(uint(numSigners))
   279  		require.NoError(t, err)
   280  
   281  		// encode
   282  		prefixed, err := signature.EncodeSignersToIndices(committee, signers)
   283  		require.NoError(t, err)
   284  
   285  		signerIndices, err := signature.CompareAndExtract(committee, prefixed)
   286  		require.NoError(t, err)
   287  
   288  		// check verify signer indices
   289  		correctEncoding(t, signerIndices, committee, signers)
   290  	})
   291  }
   292  
   293  // Test_DecodeSignerIndicesToIdentifiers uses fuzzy-testing framework Rapid to test the method DecodeSignerIndicesToIdentifiers:
   294  //   - we generate a set of authorized signer: `identities`
   295  //   - part of this set is sampled as signers: `signers`
   296  //   - We encode the set using `EncodeSignersToIndices` (tested before) and then decode it.
   297  //     Thereby we should recover the original input. Caution, the order might be different,
   298  //     so we sort both sets.
   299  func Test_DecodeSignerIndicesToIdentifiers(t *testing.T) {
   300  	rapid.Check(t, func(t *rapid.T) {
   301  		// select total committee size, number of random beacon signers and number of staking signers
   302  		committeeSize := rapid.IntRange(1, 272).Draw(t, "committeeSize")
   303  		numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners")
   304  
   305  		// create committee
   306  		identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical)
   307  		committee := identities.NodeIDs()
   308  		signers, err := committee.Sample(uint(numSigners))
   309  		require.NoError(t, err)
   310  		sort.Sort(signers)
   311  
   312  		// encode
   313  		signerIndices, err := signature.EncodeSignersToIndices(committee, signers)
   314  		require.NoError(t, err)
   315  
   316  		// decode and verify
   317  		decodedSigners, err := signature.DecodeSignerIndicesToIdentifiers(committee, signerIndices)
   318  		require.NoError(t, err)
   319  		sort.Sort(decodedSigners)
   320  		require.Equal(t, signers, decodedSigners)
   321  	})
   322  }
   323  
   324  // Test_DecodeSignerIndicesToIdentities uses fuzzy-testing framework Rapid to test the method DecodeSignerIndicesToIdentities:
   325  // * we generate a set of authorized signer: `identities`
   326  // * part of this set is sampled as singers: `signers`
   327  // * We encode the set using `EncodeSignersToIndices` (tested before) and then decode it.
   328  //   Thereby we should recover the original input. Caution, the order might be different,
   329  //   so we sort both sets.
   330  // Note: this is _almost_ the same test as `Test_DecodeSignerIndicesToIdentifiers`. However, in the other
   331  // test, we decode to node IDs; while in this test, we decode to full _Identities_.
   332  
   333  const UpperBoundCommitteeSize = 272
   334  
   335  func Test_DecodeSignerIndicesToIdentities(t *testing.T) {
   336  
   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)
   344  		signers, err := identities.Sample(uint(numSigners))
   345  		require.NoError(t, err)
   346  
   347  		// encode
   348  		signerIndices, err := signature.EncodeSignersToIndices(identities.NodeIDs(), signers.NodeIDs())
   349  		require.NoError(t, err)
   350  
   351  		// decode and verify
   352  		decodedSigners, err := signature.DecodeSignerIndicesToIdentities(identities, signerIndices)
   353  		require.NoError(t, err)
   354  		require.Equal(t, signers.Sort(flow.Canonical), decodedSigners.Sort(flow.Canonical))
   355  	})
   356  }
   357  
   358  // sampleSigners takes `committee` and samples to _disjoint_ subsets
   359  // (`stakingSigners` and `randomBeaconSigners`) with the specified cardinality
   360  func sampleSigners(
   361  	t *rapid.T,
   362  	committee flow.IdentifierList,
   363  	numStakingSigners int,
   364  	numRandomBeaconSigners int,
   365  ) (stakingSigners flow.IdentifierList, randomBeaconSigners flow.IdentifierList) {
   366  	if numStakingSigners+numRandomBeaconSigners > len(committee) {
   367  		panic(fmt.Sprintf("Cannot sample %d nodes out of a committee is size %d", numStakingSigners+numRandomBeaconSigners, len(committee)))
   368  	}
   369  
   370  	var err error
   371  	stakingSigners, err = committee.Sample(uint(numStakingSigners))
   372  	require.NoError(t, err)
   373  	remaining := committee.Filter(id.Not(id.In(stakingSigners...)))
   374  	randomBeaconSigners, err = remaining.Sample(uint(numRandomBeaconSigners))
   375  	require.NoError(t, err)
   376  	return
   377  }
   378  
   379  // correctEncoding verifies that the given indices conform to the following specification:
   380  //   - indices is the _smallest_ possible byte slice that contains at least `len(canonicalIdentifiers)` number of _bits_
   381  //   - Let indices[i] denote the ith bit of `indices`. We verify that:
   382  //
   383  // .                            ┌ 1 if and only if canonicalIdentifiers[i] is in `subset`
   384  // .               indices[i] = └ 0 otherwise
   385  //
   386  // This function can be used to verify signer indices as well as signature type encoding
   387  func correctEncoding(t require.TestingT, indices []byte, canonicalIdentifiers flow.IdentifierList, subset flow.IdentifierList) {
   388  	// verify that indices has correct length
   389  	numberBits := 8 * len(indices)
   390  	require.True(t, numberBits >= len(canonicalIdentifiers), "signerIndices has too few bits")
   391  	require.True(t, numberBits-len(canonicalIdentifiers) < 8, fmt.Sprintf("signerIndices %v is padded with too many %v bits",
   392  		numberBits, len(canonicalIdentifiers)))
   393  
   394  	// convert canonicalIdentifiers to map Identifier -> index
   395  	m := make(map[flow.Identifier]int)
   396  	for i, id := range canonicalIdentifiers {
   397  		m[id] = i
   398  	}
   399  
   400  	// make sure that every member of the subset is represented by a 1 in `indices`
   401  	for _, id := range subset {
   402  		bitIndex := m[id]
   403  		require.True(t, bitutils.ReadBit(indices, bitIndex) == 1)
   404  		delete(m, id)
   405  	}
   406  
   407  	// as we delete all IDs in subset from m, the remaining ID in `m` should be represented by a 0 in `indices`
   408  	for id := range m {
   409  		bitIndex := m[id]
   410  		require.True(t, bitutils.ReadBit(indices, bitIndex) == 0)
   411  	}
   412  
   413  	// the padded bits should also all be 0:
   414  	for i := len(canonicalIdentifiers); i < 8*len(indices); i++ {
   415  		require.True(t, bitutils.ReadBit(indices, i) == 0)
   416  	}
   417  }