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  }