github.com/koko1123/flow-go-1@v0.29.6/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/koko1123/flow-go-1/ledger/common/bitutils"
    12  	"github.com/koko1123/flow-go-1/model/flow"
    13  	"github.com/koko1123/flow-go-1/model/flow/filter"
    14  	"github.com/koko1123/flow-go-1/model/flow/filter/id"
    15  	"github.com/koko1123/flow-go-1/model/flow/order"
    16  	"github.com/koko1123/flow-go-1/module/signature"
    17  	"github.com/koko1123/flow-go-1/utils/unittest"
    18  )
    19  
    20  // TestEncodeDecodeIdentities verifies the two path of encoding -> decoding:
    21  //  1. Identifiers --encode--> Indices --decode--> Identifiers
    22  //  2. for the decoding step, we offer an optimized convenience function to directly
    23  //     decode to full identities: Indices --decode--> Identities
    24  func TestEncodeDecodeIdentities(t *testing.T) {
    25  	canonicalIdentities := unittest.IdentityListFixture(20)
    26  	canonicalIdentifiers := canonicalIdentities.NodeIDs()
    27  	for s := 0; s < 20; s++ {
    28  		for e := s; e < 20; e++ {
    29  			var signers = canonicalIdentities[s:e]
    30  
    31  			// encoding
    32  			indices, err := signature.EncodeSignersToIndices(canonicalIdentities.NodeIDs(), signers.NodeIDs())
    33  			require.NoError(t, err)
    34  
    35  			// decoding option 1: decode to Identifiers
    36  			decodedIDs, err := signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, indices)
    37  			require.NoError(t, err)
    38  			require.Equal(t, signers.NodeIDs(), decodedIDs)
    39  
    40  			// decoding option 2: decode to Identities
    41  			decodedIdentities, err := signature.DecodeSignerIndicesToIdentities(canonicalIdentities, indices)
    42  			require.NoError(t, err)
    43  			require.Equal(t, signers, decodedIdentities)
    44  		}
    45  	}
    46  }
    47  
    48  func TestEncodeDecodeIdentitiesFail(t *testing.T) {
    49  	canonicalIdentities := unittest.IdentityListFixture(20)
    50  	canonicalIdentifiers := canonicalIdentities.NodeIDs()
    51  	signers := canonicalIdentities[3:19]
    52  	validIndices, err := signature.EncodeSignersToIndices(canonicalIdentities.NodeIDs(), signers.NodeIDs())
    53  	require.NoError(t, err)
    54  
    55  	_, err = signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, validIndices)
    56  	require.NoError(t, err)
    57  
    58  	invalidSum := make([]byte, len(validIndices))
    59  	copy(invalidSum, validIndices)
    60  	if invalidSum[0] == byte(0) {
    61  		invalidSum[0] = byte(1)
    62  	} else {
    63  		invalidSum[0] = byte(0)
    64  	}
    65  	_, err = signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, invalidSum)
    66  	require.True(t, signature.IsInvalidSignerIndicesError(err), err)
    67  	require.ErrorIs(t, err, signature.ErrInvalidChecksum, err)
    68  
    69  	incompatibleLength := append(validIndices, byte(0))
    70  	_, err = signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, incompatibleLength)
    71  	require.True(t, signature.IsInvalidSignerIndicesError(err), err)
    72  	require.False(t, signature.IsInvalidSignerIndicesError(signature.NewInvalidSigTypesErrorf("sdf")))
    73  	require.ErrorIs(t, err, signature.ErrIncompatibleBitVectorLength, err)
    74  
    75  	illegallyPadded := make([]byte, len(validIndices))
    76  	copy(illegallyPadded, validIndices)
    77  	illegallyPadded[len(illegallyPadded)-1]++
    78  	_, err = signature.DecodeSignerIndicesToIdentifiers(canonicalIdentifiers, illegallyPadded)
    79  	require.True(t, signature.IsInvalidSignerIndicesError(err), err)
    80  	require.ErrorIs(t, err, signature.ErrIllegallyPaddedBitVector, err)
    81  }
    82  
    83  func TestEncodeIdentity(t *testing.T) {
    84  	only := unittest.IdentifierListFixture(1)
    85  	indices, err := signature.EncodeSignersToIndices(only, only)
    86  	require.NoError(t, err)
    87  	// byte(1,0,0,0,0,0,0,0)
    88  	require.Equal(t, []byte{byte(1 << 7)}, indices[signature.CheckSumLen:])
    89  }
    90  
    91  // TestEncodeFail verifies that an error is returned in case some signer is not part
    92  // of the set of canonicalIdentifiers
    93  func TestEncodeFail(t *testing.T) {
    94  	fullIdentities := unittest.IdentifierListFixture(20)
    95  	_, err := signature.EncodeSignersToIndices(fullIdentities[1:], fullIdentities[:10])
    96  	require.Error(t, err)
    97  }
    98  
    99  // Test_EncodeSignerToIndicesAndSigType uses fuzzy-testing framework Rapid to
   100  // test the method EncodeSignerToIndicesAndSigType:
   101  // * we generate a set of authorized signer: `committeeIdentities`
   102  // * part of this set is sampled as staking singers: `stakingSigners`
   103  // * another part of `committeeIdentities` is sampled as beacon singers: `beaconSigners`
   104  // * we encode the set and check that the results conform to the protocol specification
   105  func Test_EncodeSignerToIndicesAndSigType(t *testing.T) {
   106  	rapid.Check(t, func(t *rapid.T) {
   107  		// select total committee size, number of random beacon signers and number of staking signers
   108  		committeeSize := rapid.IntRange(1, 272).Draw(t, "committeeSize").(int)
   109  		numStakingSigners := rapid.IntRange(0, committeeSize).Draw(t, "numStakingSigners").(int)
   110  		numRandomBeaconSigners := rapid.IntRange(0, committeeSize-numStakingSigners).Draw(t, "numRandomBeaconSigners").(int)
   111  
   112  		// create committee
   113  		committeeIdentities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical)
   114  		committee := committeeIdentities.NodeIDs()
   115  		stakingSigners, beaconSigners := sampleSigners(committee, numStakingSigners, numRandomBeaconSigners)
   116  
   117  		// encode
   118  		prefixed, sigTypes, err := signature.EncodeSignerToIndicesAndSigType(committee, stakingSigners, beaconSigners)
   119  		require.NoError(t, err)
   120  
   121  		signerIndices, err := signature.CompareAndExtract(committeeIdentities.NodeIDs(), prefixed)
   122  		require.NoError(t, err)
   123  
   124  		// check verify signer indices
   125  		unorderedSigners := stakingSigners.Union(beaconSigners) // caution, the Union operation potentially changes the ordering
   126  		correctEncoding(t, signerIndices, committee, unorderedSigners)
   127  
   128  		// check sigTypes
   129  		canSigners := committeeIdentities.Filter(filter.HasNodeID(unorderedSigners...)).NodeIDs() // generates list of signer IDs in canonical order
   130  		correctEncoding(t, sigTypes, canSigners, beaconSigners)
   131  	})
   132  }
   133  
   134  // Test_DecodeSigTypeToStakingAndBeaconSigners uses fuzzy-testing framework Rapid to
   135  // test the method DecodeSigTypeToStakingAndBeaconSigners:
   136  //   - we generate a set of authorized signer: `committeeIdentities`
   137  //   - part of this set is sampled as staking singers: `stakingSigners`
   138  //   - another part of `committeeIdentities` is sampled as beacon singers: `beaconSigners`
   139  //   - we encode the set and check that the results conform to the protocol specification
   140  //   - We encode the set using `EncodeSignerToIndicesAndSigType` (tested before) and then decode it.
   141  //     Thereby we should recover the original input. Caution, the order might be different,
   142  //     so we sort both sets.
   143  func Test_DecodeSigTypeToStakingAndBeaconSigners(t *testing.T) {
   144  	rapid.Check(t, func(t *rapid.T) {
   145  		// select total committee size, number of random beacon signers and number of staking signers
   146  		committeeSize := rapid.IntRange(1, 272).Draw(t, "committeeSize").(int)
   147  		numStakingSigners := rapid.IntRange(0, committeeSize).Draw(t, "numStakingSigners").(int)
   148  		numRandomBeaconSigners := rapid.IntRange(0, committeeSize-numStakingSigners).Draw(t, "numRandomBeaconSigners").(int)
   149  
   150  		// create committee
   151  		committeeIdentities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical)
   152  		committee := committeeIdentities.NodeIDs()
   153  		stakingSigners, beaconSigners := sampleSigners(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, 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(filter.Or(filter.HasNodeID(stakingSigners...), filter.HasNodeID(beaconSigners...))) // signer identities in canonical order
   168  		if len(stakingSigners)+len(decBeaconSigners) > 0 {
   169  			require.Equal(t, sigIdentities, decSignerIdentites)
   170  		}
   171  		if len(stakingSigners) == 0 {
   172  			require.Empty(t, decStakingSigners)
   173  		} else {
   174  			require.Equal(t, committeeIdentities.Filter(filter.HasNodeID(stakingSigners...)), decStakingSigners)
   175  		}
   176  		if len(decBeaconSigners) == 0 {
   177  			require.Empty(t, decBeaconSigners)
   178  		} else {
   179  			require.Equal(t, committeeIdentities.Filter(filter.HasNodeID(beaconSigners...)), decBeaconSigners)
   180  		}
   181  	})
   182  }
   183  
   184  func Test_ValidPaddingErrIncompatibleBitVectorLength(t *testing.T) {
   185  	var signers flow.IdentityList
   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)
   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)
   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)
   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)
   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)
   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.IdentityList
   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)
   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)
   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").(int)
   274  		numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners").(int)
   275  
   276  		// create committee
   277  		identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical)
   278  		committee := identities.NodeIDs()
   279  		signers := committee.Sample(uint(numSigners))
   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").(int)
   303  		numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners").(int)
   304  
   305  		// create committee
   306  		identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical)
   307  		committee := identities.NodeIDs()
   308  		signers := committee.Sample(uint(numSigners))
   309  		sort.Sort(signers)
   310  
   311  		// encode
   312  		signerIndices, err := signature.EncodeSignersToIndices(committee, signers)
   313  		require.NoError(t, err)
   314  
   315  		// decode and verify
   316  		decodedSigners, err := signature.DecodeSignerIndicesToIdentifiers(committee, signerIndices)
   317  		require.NoError(t, err)
   318  		sort.Sort(decodedSigners)
   319  		require.Equal(t, signers, decodedSigners)
   320  	})
   321  }
   322  
   323  // Test_DecodeSignerIndicesToIdentities uses fuzzy-testing framework Rapid to test the method DecodeSignerIndicesToIdentities:
   324  // * we generate a set of authorized signer: `identities`
   325  // * part of this set is sampled as singers: `signers`
   326  // * We encode the set using `EncodeSignersToIndices` (tested before) and then decode it.
   327  //   Thereby we should recover the original input. Caution, the order might be different,
   328  //   so we sort both sets.
   329  // Note: this is _almost_ the same test as `Test_DecodeSignerIndicesToIdentifiers`. However, in the other
   330  // test, we decode to node IDs; while in this test, we decode to full _Identities_.
   331  
   332  const UpperBoundCommitteeSize = 272
   333  
   334  func Test_DecodeSignerIndicesToIdentities(t *testing.T) {
   335  
   336  	rapid.Check(t, func(t *rapid.T) {
   337  		// select total committee size, number of random beacon signers and number of staking signers
   338  		committeeSize := rapid.IntRange(1, UpperBoundCommitteeSize).Draw(t, "committeeSize").(int)
   339  		numSigners := rapid.IntRange(0, committeeSize).Draw(t, "numSigners").(int)
   340  
   341  		// create committee
   342  		identities := unittest.IdentityListFixture(committeeSize, unittest.WithRole(flow.RoleConsensus)).Sort(order.Canonical)
   343  		signers := identities.Sample(uint(numSigners))
   344  
   345  		// encode
   346  		signerIndices, err := signature.EncodeSignersToIndices(identities.NodeIDs(), signers.NodeIDs())
   347  		require.NoError(t, err)
   348  
   349  		// decode and verify
   350  		decodedSigners, err := signature.DecodeSignerIndicesToIdentities(identities, signerIndices)
   351  		require.NoError(t, err)
   352  		require.Equal(t, signers.Sort(order.Canonical), decodedSigners.Sort(order.Canonical))
   353  	})
   354  }
   355  
   356  // sampleSigners takes `committee` and samples to _disjoint_ subsets
   357  // (`stakingSigners` and `randomBeaconSigners`) with the specified cardinality
   358  func sampleSigners(
   359  	committee flow.IdentifierList,
   360  	numStakingSigners int,
   361  	numRandomBeaconSigners int,
   362  ) (stakingSigners flow.IdentifierList, randomBeaconSigners flow.IdentifierList) {
   363  	if numStakingSigners+numRandomBeaconSigners > len(committee) {
   364  		panic(fmt.Sprintf("Cannot sample %d nodes out of a committee is size %d", numStakingSigners+numRandomBeaconSigners, len(committee)))
   365  	}
   366  
   367  	stakingSigners = committee.Sample(uint(numStakingSigners))
   368  	remaining := committee.Filter(id.Not(id.In(stakingSigners...)))
   369  	randomBeaconSigners = remaining.Sample(uint(numRandomBeaconSigners))
   370  	return
   371  }
   372  
   373  // correctEncoding verifies that the given indices conform to the following specification:
   374  //   - indices is the _smallest_ possible byte slice that contains at least `len(canonicalIdentifiers)` number of _bits_
   375  //   - Let indices[i] denote the ith bit of `indices`. We verify that:
   376  //
   377  // .                            ┌ 1 if and only if canonicalIdentifiers[i] is in `subset`
   378  // .               indices[i] = └ 0 otherwise
   379  //
   380  // This function can be used to verify signer indices as well as signature type encoding
   381  func correctEncoding(t require.TestingT, indices []byte, canonicalIdentifiers flow.IdentifierList, subset flow.IdentifierList) {
   382  	// verify that indices has correct length
   383  	numberBits := 8 * len(indices)
   384  	require.True(t, numberBits >= len(canonicalIdentifiers), "signerIndices has too few bits")
   385  	require.True(t, numberBits-len(canonicalIdentifiers) < 8, fmt.Sprintf("signerIndices %v is padded with too many %v bits",
   386  		numberBits, len(canonicalIdentifiers)))
   387  
   388  	// convert canonicalIdentifiers to map Identifier -> index
   389  	m := make(map[flow.Identifier]int)
   390  	for i, id := range canonicalIdentifiers {
   391  		m[id] = i
   392  	}
   393  
   394  	// make sure that every member of the subset is represented by a 1 in `indices`
   395  	for _, id := range subset {
   396  		bitIndex := m[id]
   397  		require.True(t, bitutils.ReadBit(indices, bitIndex) == 1)
   398  		delete(m, id)
   399  	}
   400  
   401  	// as we delete all IDs in subset from m, the remaining ID in `m` should be represented by a 0 in `indices`
   402  	for id := range m {
   403  		bitIndex := m[id]
   404  		require.True(t, bitutils.ReadBit(indices, bitIndex) == 0)
   405  	}
   406  
   407  	// the padded bits should also all be 0:
   408  	for i := len(canonicalIdentifiers); i < 8*len(indices); i++ {
   409  		require.True(t, bitutils.ReadBit(indices, i) == 0)
   410  	}
   411  }