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 }