github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/signature/packer_test.go (about) 1 package signature 2 3 import ( 4 "fmt" 5 "math/rand" 6 "testing" 7 8 "github.com/stretchr/testify/mock" 9 "github.com/stretchr/testify/require" 10 11 "github.com/onflow/flow-go/consensus/hotstuff" 12 "github.com/onflow/flow-go/consensus/hotstuff/mocks" 13 "github.com/onflow/flow-go/consensus/hotstuff/model" 14 "github.com/onflow/flow-go/model/flow" 15 "github.com/onflow/flow-go/module/signature" 16 "github.com/onflow/flow-go/utils/unittest" 17 ) 18 19 func newPacker(identities flow.IdentitySkeletonList) *ConsensusSigDataPacker { 20 // mock consensus committee 21 committee := &mocks.DynamicCommittee{} 22 committee.On("IdentitiesByEpoch", mock.Anything).Return( 23 func(_ uint64) flow.IdentitySkeletonList { 24 return identities 25 }, 26 nil, 27 ) 28 29 return NewConsensusSigDataPacker(committee) 30 } 31 32 func makeBlockSigData(committee flow.IdentitySkeletonList) *hotstuff.BlockSignatureData { 33 blockSigData := &hotstuff.BlockSignatureData{ 34 StakingSigners: []flow.Identifier{ 35 committee[0].NodeID, // A 36 committee[2].NodeID, // C 37 }, 38 RandomBeaconSigners: []flow.Identifier{ 39 committee[3].NodeID, // D 40 committee[5].NodeID, // F 41 }, 42 AggregatedStakingSig: unittest.SignatureFixture(), 43 AggregatedRandomBeaconSig: unittest.SignatureFixture(), 44 ReconstructedRandomBeaconSig: unittest.SignatureFixture(), 45 } 46 return blockSigData 47 } 48 49 // test that a packed data can be unpacked 50 // given the consensus committee [A, B, C, D, E, F] 51 // [B,D,F] are random beacon nodes 52 // [A,C,E] are non-random beacon nodes 53 // aggregated staking sigs are from [A,C] 54 // aggregated random beacon sigs are from [D,F] 55 func TestPackUnpack(t *testing.T) { 56 // prepare data for testing 57 committee := unittest.IdentityListFixture(6, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical[flow.Identity]).ToSkeleton() 58 view := rand.Uint64() 59 blockSigData := makeBlockSigData(committee) 60 61 // create packer with the committee 62 packer := newPacker(committee) 63 64 // pack & unpack 65 signerIndices, sig, err := packer.Pack(view, blockSigData) 66 require.NoError(t, err) 67 68 signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices) 69 require.NoError(t, err) 70 71 unpacked, err := packer.Unpack(signers, sig) 72 require.NoError(t, err) 73 74 // check that the unpacked data match with the original data 75 require.Equal(t, blockSigData.StakingSigners, unpacked.StakingSigners) 76 require.Equal(t, blockSigData.RandomBeaconSigners, unpacked.RandomBeaconSigners) 77 require.Equal(t, blockSigData.AggregatedStakingSig, unpacked.AggregatedStakingSig) 78 require.Equal(t, blockSigData.AggregatedRandomBeaconSig, unpacked.AggregatedRandomBeaconSig) 79 require.Equal(t, blockSigData.ReconstructedRandomBeaconSig, unpacked.ReconstructedRandomBeaconSig) 80 81 // check the packed signer IDs 82 var expectedSignerIDs flow.IdentifierList 83 expectedSignerIDs = append(expectedSignerIDs, blockSigData.StakingSigners...) 84 expectedSignerIDs = append(expectedSignerIDs, blockSigData.RandomBeaconSigners...) 85 require.Equal(t, expectedSignerIDs, signers.NodeIDs()) 86 } 87 88 // TestUnpack_EmptySignerList verifies that `Unpack` gracefully handles the edge case 89 // of an empty signer list, as such could be an input from a byzantine node. 90 func TestPackUnpack_EmptySigners(t *testing.T) { 91 // encode SignatureData with empty SigType vector (this could be an input from a byzantine node) 92 byzantineInput := model.SignatureData{ 93 SigType: []byte{}, 94 AggregatedStakingSig: unittest.SignatureFixture(), 95 AggregatedRandomBeaconSig: unittest.SignatureFixture(), 96 ReconstructedRandomBeaconSig: unittest.SignatureFixture(), 97 } 98 encoder := new(model.SigDataPacker) 99 sig, err := encoder.Encode(&byzantineInput) 100 require.NoError(t, err) 101 102 // create packer with a non-empty committee (honest node trying to decode the sig data) 103 committee := unittest.IdentityListFixture(6, unittest.WithRole(flow.RoleConsensus)).ToSkeleton() 104 packer := newPacker(committee) 105 unpacked, err := packer.Unpack(make(flow.IdentitySkeletonList, 0), sig) 106 require.NoError(t, err) 107 108 // check that the unpack data match with the original data 109 require.Empty(t, unpacked.StakingSigners) 110 require.Empty(t, unpacked.RandomBeaconSigners) 111 require.Equal(t, byzantineInput.AggregatedStakingSig, unpacked.AggregatedStakingSig) 112 require.Equal(t, byzantineInput.AggregatedRandomBeaconSig, unpacked.AggregatedRandomBeaconSig) 113 require.Equal(t, byzantineInput.ReconstructedRandomBeaconSig, unpacked.ReconstructedRandomBeaconSig) 114 } 115 116 // if signed by 60 staking nodes, and 50 random beacon nodes among a 200 nodes committee, 117 // it's able to pack and unpack 118 func TestPackUnpackManyNodes(t *testing.T) { 119 // prepare data for testing 120 committee := unittest.IdentityListFixture(200, unittest.WithRole(flow.RoleConsensus)).ToSkeleton() 121 view := rand.Uint64() 122 blockSigData := makeBlockSigData(committee) 123 stakingSigners := make([]flow.Identifier, 0) 124 for i := 0; i < 60; i++ { 125 stakingSigners = append(stakingSigners, committee[i].NodeID) 126 } 127 randomBeaconSigners := make([]flow.Identifier, 0) 128 for i := 100; i < 100+50; i++ { 129 randomBeaconSigners = append(randomBeaconSigners, committee[i].NodeID) 130 } 131 blockSigData.StakingSigners = stakingSigners 132 blockSigData.RandomBeaconSigners = randomBeaconSigners 133 134 // create packer with the committee 135 packer := newPacker(committee) 136 137 // pack & unpack 138 signerIndices, sig, err := packer.Pack(view, blockSigData) 139 require.NoError(t, err) 140 141 signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices) 142 require.NoError(t, err) 143 144 unpacked, err := packer.Unpack(signers, sig) 145 require.NoError(t, err) 146 147 // check that the unpack data match with the original data 148 require.Equal(t, blockSigData.StakingSigners, unpacked.StakingSigners) 149 require.Equal(t, blockSigData.RandomBeaconSigners, unpacked.RandomBeaconSigners) 150 require.Equal(t, blockSigData.AggregatedStakingSig, unpacked.AggregatedStakingSig) 151 require.Equal(t, blockSigData.AggregatedRandomBeaconSig, unpacked.AggregatedRandomBeaconSig) 152 require.Equal(t, blockSigData.ReconstructedRandomBeaconSig, unpacked.ReconstructedRandomBeaconSig) 153 154 // check the packed signer IDs 155 var expectedSignerIDs flow.IdentifierList 156 expectedSignerIDs = append(expectedSignerIDs, blockSigData.StakingSigners...) 157 expectedSignerIDs = append(expectedSignerIDs, blockSigData.RandomBeaconSigners...) 158 require.Equal(t, expectedSignerIDs, signers.NodeIDs()) 159 } 160 161 // if the sig data can not be decoded, return model.InvalidFormatError 162 func TestFailToDecode(t *testing.T) { 163 // prepare data for testing 164 committee := unittest.IdentityListFixture(6, unittest.WithRole(flow.RoleConsensus)).ToSkeleton() 165 view := rand.Uint64() 166 blockSigData := makeBlockSigData(committee) 167 168 // create packer with the committee 169 packer := newPacker(committee) 170 171 signerIndices, sig, err := packer.Pack(view, blockSigData) 172 require.NoError(t, err) 173 174 signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices) 175 require.NoError(t, err) 176 177 // prepare invalid data by modifying the valid data and unpack: 178 invalidSigData := sig[1:] 179 _, err = packer.Unpack(signers, invalidSigData) 180 require.True(t, model.IsInvalidFormatError(err)) 181 } 182 183 // TestMismatchSignerIDs 184 // if the signer IDs doesn't match, return InvalidFormatError 185 func TestMismatchSignerIDs(t *testing.T) { 186 // prepare data for testing 187 committee := unittest.IdentityListFixture(9, unittest.WithRole(flow.RoleConsensus)).ToSkeleton() 188 view := rand.Uint64() 189 blockSigData := makeBlockSigData(committee[:6]) 190 191 // create packer with the committee 192 packer := newPacker(committee) 193 194 signerIndices, sig, err := packer.Pack(view, blockSigData) 195 require.NoError(t, err) 196 197 signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices) 198 require.NoError(t, err) 199 200 // prepare invalid signers by modifying the valid signers 201 // remove the first signer 202 invalidSignerIDs := signers[1:] 203 204 _, err = packer.Unpack(invalidSignerIDs, sig) 205 require.True(t, model.IsInvalidFormatError(err)) 206 207 // with additional signer 208 // 9 nodes committee would require two bytes for sig type, the additional byte 209 // would cause the sig type and signer IDs to be mismatch 210 invalidSignerIDs = committee 211 misPacked, err := packer.Unpack(invalidSignerIDs, sig) 212 require.Error(t, err, fmt.Sprintf("packed signers: %v", misPacked)) 213 require.True(t, model.IsInvalidFormatError(err)) 214 } 215 216 // if sig type doesn't match, return InvalidFormatError 217 func TestInvalidSigType(t *testing.T) { 218 // prepare data for testing 219 committee := unittest.IdentityListFixture(6, unittest.WithRole(flow.RoleConsensus)).ToSkeleton() 220 view := rand.Uint64() 221 blockSigData := makeBlockSigData(committee) 222 223 // create packer with the committee 224 packer := newPacker(committee) 225 226 signerIndices, sig, err := packer.Pack(view, blockSigData) 227 require.NoError(t, err) 228 229 signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices) 230 require.NoError(t, err) 231 232 data, err := packer.Decode(sig) 233 require.NoError(t, err) 234 235 data.SigType = []byte{1} 236 237 encoded, err := packer.Encode(data) 238 require.NoError(t, err) 239 240 _, err = packer.Unpack(signers, encoded) 241 require.True(t, model.IsInvalidFormatError(err)) 242 } 243 244 // TestPackUnpackWithoutRBAggregatedSig test that a packed data without random beacon signers and 245 // aggregated random beacon sig can be correctly packed and unpacked 246 // given the consensus committee [A, B, C] 247 // [A, B, C] are non-random beacon nodes 248 // aggregated staking sigs are from [A,B,C] 249 // no aggregated random beacon sigs 250 // no random beacon signers 251 func TestPackUnpackWithoutRBAggregatedSig(t *testing.T) { 252 // prepare data for testing 253 committee := unittest.IdentityListFixture(3, unittest.WithRole(flow.RoleConsensus)).ToSkeleton() 254 view := rand.Uint64() 255 256 blockSigData := &hotstuff.BlockSignatureData{ 257 StakingSigners: committee.NodeIDs(), 258 RandomBeaconSigners: nil, 259 AggregatedStakingSig: unittest.SignatureFixture(), 260 AggregatedRandomBeaconSig: nil, 261 ReconstructedRandomBeaconSig: unittest.SignatureFixture(), 262 } 263 264 // create packer with the committee 265 packer := newPacker(committee) 266 267 // pack & unpack 268 signerIndices, sig, err := packer.Pack(view, blockSigData) 269 require.NoError(t, err) 270 271 signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices) 272 require.NoError(t, err) 273 274 unpacked, err := packer.Unpack(signers, sig) 275 require.NoError(t, err) 276 277 // check that the unpack data match with the original data 278 require.Equal(t, blockSigData.StakingSigners, unpacked.StakingSigners) 279 require.Equal(t, blockSigData.AggregatedStakingSig, unpacked.AggregatedStakingSig) 280 require.Equal(t, blockSigData.ReconstructedRandomBeaconSig, unpacked.ReconstructedRandomBeaconSig) 281 282 // we need to specifically test if it's empty, it has to be by test definition 283 require.Empty(t, unpacked.RandomBeaconSigners) 284 require.Empty(t, unpacked.AggregatedRandomBeaconSig) 285 286 // check the packed signer IDs 287 expectedSignerIDs := append(flow.IdentifierList{}, blockSigData.StakingSigners...) 288 require.Equal(t, expectedSignerIDs, signers.NodeIDs()) 289 } 290 291 // TestPackWithoutRBAggregatedSig tests that packer correctly handles BlockSignatureData 292 // with different structure format, more specifically there is no difference between 293 // nil and empty slices for RandomBeaconSigners and AggregatedRandomBeaconSig. 294 func TestPackWithoutRBAggregatedSig(t *testing.T) { 295 identities := unittest.IdentityListFixture(3, unittest.WithRole(flow.RoleConsensus)).ToSkeleton() 296 committee := identities.NodeIDs() 297 298 // prepare data for testing 299 view := rand.Uint64() 300 301 aggregatedSig := unittest.SignatureFixture() 302 reconstructedSig := unittest.SignatureFixture() 303 304 blockSigDataWithEmptySlices := &hotstuff.BlockSignatureData{ 305 StakingSigners: committee, 306 RandomBeaconSigners: []flow.Identifier{}, 307 AggregatedStakingSig: aggregatedSig, 308 AggregatedRandomBeaconSig: []byte{}, 309 ReconstructedRandomBeaconSig: reconstructedSig, 310 } 311 312 blockSigDataWithNils := &hotstuff.BlockSignatureData{ 313 StakingSigners: committee, 314 RandomBeaconSigners: nil, 315 AggregatedStakingSig: aggregatedSig, 316 AggregatedRandomBeaconSig: nil, 317 ReconstructedRandomBeaconSig: reconstructedSig, 318 } 319 320 // create packer with the committee 321 packer := newPacker(identities) 322 323 // pack 324 signerIDs_A, sig_A, err := packer.Pack(view, blockSigDataWithEmptySlices) 325 require.NoError(t, err) 326 327 signerIDs_B, sig_B, err := packer.Pack(view, blockSigDataWithNils) 328 require.NoError(t, err) 329 330 // should be the same 331 require.Equal(t, signerIDs_A, signerIDs_B) 332 require.Equal(t, sig_A, sig_B) 333 }