github.com/ethersphere/bee/v2@v2.2.0/pkg/soc/soc_test.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package soc_test 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "encoding/hex" 11 "errors" 12 "testing" 13 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethersphere/bee/v2/pkg/cac" 16 "github.com/ethersphere/bee/v2/pkg/crypto" 17 "github.com/ethersphere/bee/v2/pkg/soc" 18 "github.com/ethersphere/bee/v2/pkg/swarm" 19 ) 20 21 func TestNew(t *testing.T) { 22 t.Parallel() 23 24 payload := []byte("foo") 25 ch, err := cac.New(payload) 26 if err != nil { 27 t.Fatal(err) 28 } 29 30 id := make([]byte, swarm.HashSize) 31 s := soc.New(id, ch) 32 33 // check SOC fields 34 if !bytes.Equal(s.ID(), id) { 35 t.Fatalf("id mismatch. got %x want %x", s.ID(), id) 36 } 37 38 chunkData := s.WrappedChunk().Data() 39 spanBytes := make([]byte, swarm.SpanSize) 40 binary.LittleEndian.PutUint64(spanBytes, uint64(len(payload))) 41 if !bytes.Equal(chunkData[:swarm.SpanSize], spanBytes) { 42 t.Fatalf("span mismatch. got %x want %x", chunkData[:swarm.SpanSize], spanBytes) 43 } 44 45 if !bytes.Equal(chunkData[swarm.SpanSize:], payload) { 46 t.Fatalf("payload mismatch. got %x want %x", chunkData[swarm.SpanSize:], payload) 47 } 48 } 49 50 func TestReplica(t *testing.T) { 51 sig, err := hex.DecodeString("5acd384febc133b7b245e5ddc62d82d2cded9182d2716126cd8844509af65a053deb418208027f548e3e88343af6f84a8772fb3cebc0a1833a0ea7ec0c1348311b") 52 if err != nil { 53 t.Fatal(err) 54 } 55 56 payload := []byte("foo") 57 ch, err := cac.New(payload) 58 if err != nil { 59 t.Fatal(err) 60 } 61 62 id := make([]byte, swarm.HashSize) 63 s, err := soc.NewSigned(id, ch, swarm.ReplicasOwner, sig) 64 if err != nil { 65 t.Fatal(err) 66 } 67 68 ch, err = s.Chunk() 69 if err != nil { 70 t.Fatal(err) 71 } 72 sch, err := soc.FromChunk(swarm.NewChunk(swarm.EmptyAddress, ch.Data())) 73 if err != nil { 74 t.Fatal(err) 75 } 76 ch, err = sch.Chunk() 77 if err != nil { 78 t.Fatal(err) 79 } 80 if !soc.Valid(ch) { 81 t.Fatal("invalid soc chunk") 82 } 83 } 84 85 func TestNewSigned(t *testing.T) { 86 t.Parallel() 87 88 owner := common.HexToAddress("8d3766440f0d7b949a5e32995d09619a7f86e632") 89 // signature of hash(id + chunk address of foo) 90 sig, err := hex.DecodeString("5acd384febc133b7b245e5ddc62d82d2cded9182d2716126cd8844509af65a053deb418208027f548e3e88343af6f84a8772fb3cebc0a1833a0ea7ec0c1348311b") 91 if err != nil { 92 t.Fatal(err) 93 } 94 95 payload := []byte("foo") 96 ch, err := cac.New(payload) 97 if err != nil { 98 t.Fatal(err) 99 } 100 101 id := make([]byte, swarm.HashSize) 102 s, err := soc.NewSigned(id, ch, owner.Bytes(), sig) 103 if err != nil { 104 t.Fatal(err) 105 } 106 107 // check signed SOC fields 108 if !bytes.Equal(s.ID(), id) { 109 t.Fatalf("id mismatch. got %x want %x", s.ID(), id) 110 } 111 112 if !bytes.Equal(s.OwnerAddress(), owner.Bytes()) { 113 t.Fatalf("owner mismatch. got %x want %x", s.OwnerAddress(), owner.Bytes()) 114 } 115 116 if !bytes.Equal(s.Signature(), sig) { 117 t.Fatalf("signature mismatch. got %x want %x", s.Signature(), sig) 118 } 119 120 chunkData := s.WrappedChunk().Data() 121 spanBytes := make([]byte, swarm.SpanSize) 122 binary.LittleEndian.PutUint64(spanBytes, uint64(len(payload))) 123 if !bytes.Equal(chunkData[:swarm.SpanSize], spanBytes) { 124 t.Fatalf("span mismatch. got %x want %x", chunkData[:swarm.SpanSize], spanBytes) 125 } 126 127 if !bytes.Equal(chunkData[swarm.SpanSize:], payload) { 128 t.Fatalf("payload mismatch. got %x want %x", chunkData[swarm.SpanSize:], payload) 129 } 130 } 131 132 // TestChunk verifies that the chunk created from the SOC object 133 // corresponds to the SOC spec. 134 func TestChunk(t *testing.T) { 135 t.Parallel() 136 137 owner := common.HexToAddress("8d3766440f0d7b949a5e32995d09619a7f86e632") 138 sig, err := hex.DecodeString("5acd384febc133b7b245e5ddc62d82d2cded9182d2716126cd8844509af65a053deb418208027f548e3e88343af6f84a8772fb3cebc0a1833a0ea7ec0c1348311b") 139 if err != nil { 140 t.Fatal(err) 141 } 142 143 payload := []byte("foo") 144 ch, err := cac.New(payload) 145 if err != nil { 146 t.Fatal(err) 147 } 148 149 id := make([]byte, swarm.HashSize) 150 // creates a new signed SOC 151 s, err := soc.NewSigned(id, ch, owner.Bytes(), sig) 152 if err != nil { 153 t.Fatal(err) 154 } 155 156 sum, err := soc.Hash(id, owner.Bytes()) 157 if err != nil { 158 t.Fatal(err) 159 } 160 expectedSOCAddress := swarm.NewAddress(sum) 161 162 // creates SOC chunk 163 sch, err := s.Chunk() 164 if err != nil { 165 t.Fatal(err) 166 } 167 168 if !bytes.Equal(sch.Address().Bytes(), expectedSOCAddress.Bytes()) { 169 t.Fatalf("soc address mismatch. got %x want %x", sch.Address().Bytes(), expectedSOCAddress.Bytes()) 170 } 171 172 chunkData := sch.Data() 173 // verifies that id, signature, payload is in place in the SOC chunk 174 cursor := 0 175 if !bytes.Equal(chunkData[cursor:swarm.HashSize], id) { 176 t.Fatalf("id mismatch. got %x want %x", chunkData[cursor:swarm.HashSize], id) 177 } 178 cursor += swarm.HashSize 179 180 signature := chunkData[cursor : cursor+swarm.SocSignatureSize] 181 if !bytes.Equal(signature, sig) { 182 t.Fatalf("signature mismatch. got %x want %x", signature, sig) 183 } 184 cursor += swarm.SocSignatureSize 185 186 spanBytes := make([]byte, swarm.SpanSize) 187 binary.LittleEndian.PutUint64(spanBytes, uint64(len(payload))) 188 if !bytes.Equal(chunkData[cursor:cursor+swarm.SpanSize], spanBytes) { 189 t.Fatalf("span mismatch. got %x want %x", chunkData[cursor:cursor+swarm.SpanSize], spanBytes) 190 } 191 cursor += swarm.SpanSize 192 193 if !bytes.Equal(chunkData[cursor:], payload) { 194 t.Fatalf("payload mismatch. got %x want %x", chunkData[cursor:], payload) 195 } 196 } 197 198 func TestChunkErrorWithoutOwner(t *testing.T) { 199 t.Parallel() 200 201 payload := []byte("foo") 202 ch, err := cac.New(payload) 203 if err != nil { 204 t.Fatal(err) 205 } 206 id := make([]byte, swarm.HashSize) 207 208 // creates a new soc 209 s := soc.New(id, ch) 210 211 _, err = s.Chunk() 212 if !errors.Is(err, soc.ErrInvalidAddress) { 213 t.Fatalf("expect error. got `%v` want `%v`", err, soc.ErrInvalidAddress) 214 } 215 } 216 217 // TestSign tests whether a soc is correctly signed. 218 func TestSign(t *testing.T) { 219 t.Parallel() 220 221 privKey, err := crypto.GenerateSecp256k1Key() 222 if err != nil { 223 t.Fatal(err) 224 } 225 signer := crypto.NewDefaultSigner(privKey) 226 227 payload := []byte("foo") 228 ch, err := cac.New(payload) 229 if err != nil { 230 t.Fatal(err) 231 } 232 233 id := make([]byte, swarm.HashSize) 234 // creates the soc 235 s := soc.New(id, ch) 236 237 // signs the chunk 238 sch, err := s.Sign(signer) 239 if err != nil { 240 t.Fatal(err) 241 } 242 243 chunkData := sch.Data() 244 // get signature in the chunk 245 cursor := swarm.HashSize 246 signature := chunkData[cursor : cursor+swarm.SocSignatureSize] 247 248 // get the public key of the signer 249 publicKey, err := signer.PublicKey() 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 owner, err := crypto.NewEthereumAddress(*publicKey) 255 if err != nil { 256 t.Fatal(err) 257 } 258 259 toSignBytes, err := soc.Hash(id, ch.Address().Bytes()) 260 if err != nil { 261 t.Fatal(err) 262 } 263 264 // verifies if the owner matches 265 recoveredOwner, err := soc.RecoverAddress(signature, toSignBytes) 266 if err != nil { 267 t.Fatal(err) 268 } 269 270 if !bytes.Equal(recoveredOwner, owner) { 271 t.Fatalf("owner address mismatch. got %x want %x", recoveredOwner, owner) 272 } 273 } 274 275 // TestFromChunk verifies that valid chunk data deserializes to 276 // a fully populated soc object. 277 func TestFromChunk(t *testing.T) { 278 t.Parallel() 279 280 socAddress := swarm.MustParseHexAddress("9d453ebb73b2fedaaf44ceddcf7a0aa37f3e3d6453fea5841c31f0ea6d61dc85") 281 282 // signed soc chunk of: 283 // id: 0 284 // wrapped chunk of: `foo` 285 // owner: 0x8d3766440f0d7b949a5e32995d09619a7f86e632 286 sch := swarm.NewChunk(socAddress, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 205, 56, 79, 235, 193, 51, 183, 178, 69, 229, 221, 198, 45, 130, 210, 205, 237, 145, 130, 210, 113, 97, 38, 205, 136, 68, 80, 154, 246, 90, 5, 61, 235, 65, 130, 8, 2, 127, 84, 142, 62, 136, 52, 58, 246, 248, 74, 135, 114, 251, 60, 235, 192, 161, 131, 58, 14, 167, 236, 12, 19, 72, 49, 27, 3, 0, 0, 0, 0, 0, 0, 0, 102, 111, 111}) 287 288 cursor := swarm.HashSize + swarm.SocSignatureSize 289 data := sch.Data() 290 id := data[:swarm.HashSize] 291 sig := data[swarm.HashSize:cursor] 292 chunkData := data[cursor:] 293 294 chunkAddress := swarm.MustParseHexAddress("2387e8e7d8a48c2a9339c97c1dc3461a9a7aa07e994c5cb8b38fd7c1b3e6ea48") 295 ch := swarm.NewChunk(chunkAddress, chunkData) 296 297 signedDigest, err := soc.Hash(id, ch.Address().Bytes()) 298 if err != nil { 299 t.Fatal(err) 300 } 301 302 ownerAddress, err := soc.RecoverAddress(sig, signedDigest) 303 if err != nil { 304 t.Fatal(err) 305 } 306 307 // attempt to recover soc from signed chunk 308 recoveredSOC, err := soc.FromChunk(sch) 309 if err != nil { 310 t.Fatal(err) 311 } 312 313 // owner matching means the address was successfully recovered from 314 // payload and signature 315 if !bytes.Equal(recoveredSOC.OwnerAddress(), ownerAddress) { 316 t.Fatalf("owner address mismatch. got %x want %x", recoveredSOC.OwnerAddress(), ownerAddress) 317 } 318 319 if !bytes.Equal(recoveredSOC.ID(), id) { 320 t.Fatalf("id mismatch. got %x want %x", recoveredSOC.ID(), id) 321 } 322 323 if !bytes.Equal(recoveredSOC.Signature(), sig) { 324 t.Fatalf("signature mismatch. got %x want %x", recoveredSOC.Signature(), sig) 325 } 326 327 if !ch.Equal(recoveredSOC.WrappedChunk()) { 328 t.Fatalf("wrapped chunk mismatch. got %s want %s", recoveredSOC.WrappedChunk().Address(), ch.Address()) 329 } 330 } 331 332 func TestCreateAddress(t *testing.T) { 333 t.Parallel() 334 335 id := make([]byte, swarm.HashSize) 336 owner := common.HexToAddress("8d3766440f0d7b949a5e32995d09619a7f86e632") 337 socAddress := swarm.MustParseHexAddress("9d453ebb73b2fedaaf44ceddcf7a0aa37f3e3d6453fea5841c31f0ea6d61dc85") 338 339 addr, err := soc.CreateAddress(id, owner.Bytes()) 340 if err != nil { 341 t.Fatal(err) 342 } 343 if !addr.Equal(socAddress) { 344 t.Fatalf("soc address mismatch. got %s want %s", addr, socAddress) 345 } 346 } 347 348 func TestRecoverAddress(t *testing.T) { 349 t.Parallel() 350 351 owner := common.HexToAddress("8d3766440f0d7b949a5e32995d09619a7f86e632") 352 id := make([]byte, swarm.HashSize) 353 chunkAddress := swarm.MustParseHexAddress("2387e8e7d8a48c2a9339c97c1dc3461a9a7aa07e994c5cb8b38fd7c1b3e6ea48") 354 signedDigest, err := soc.Hash(id, chunkAddress.Bytes()) 355 if err != nil { 356 t.Fatal(err) 357 } 358 359 sig, err := hex.DecodeString("5acd384febc133b7b245e5ddc62d82d2cded9182d2716126cd8844509af65a053deb418208027f548e3e88343af6f84a8772fb3cebc0a1833a0ea7ec0c1348311b") 360 if err != nil { 361 t.Fatal(err) 362 } 363 364 // attempt to recover address from signature 365 addr, err := soc.RecoverAddress(sig, signedDigest) 366 if err != nil { 367 t.Fatal(err) 368 } 369 if !bytes.Equal(addr, owner.Bytes()) { 370 t.Fatalf("owner address mismatch. got %x want %x", addr, owner.Bytes()) 371 } 372 }