Commit 1de167ea authored by Rodrigo Q. Saramago's avatar Rodrigo Q. Saramago Committed by GitHub

Soc package refactor (#1394)

parent 99aaaa79
...@@ -84,7 +84,6 @@ func TestBzz(t *testing.T) { ...@@ -84,7 +84,6 @@ func TestBzz(t *testing.T) {
} }
fileMetadataReference, err := pipeWriteAll(bytes.NewReader(fileMetadataBytes), int64(len(fileMetadataBytes))) fileMetadataReference, err := pipeWriteAll(bytes.NewReader(fileMetadataBytes), int64(len(fileMetadataBytes)))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -171,7 +170,6 @@ func TestBzz(t *testing.T) { ...@@ -171,7 +170,6 @@ func TestBzz(t *testing.T) {
}), }),
) )
}) })
} }
func TestFeedIndirection(t *testing.T) { func TestFeedIndirection(t *testing.T) {
...@@ -219,7 +217,7 @@ func TestFeedIndirection(t *testing.T) { ...@@ -219,7 +217,7 @@ func TestFeedIndirection(t *testing.T) {
// called from the bzz endpoint. then call the bzz endpoint with // called from the bzz endpoint. then call the bzz endpoint with
// the pregenerated feed root manifest hash // the pregenerated feed root manifest hash
feedUpdate, _ := toChunk(121212, resp.Reference.Bytes()) feedUpdate := toChunk(t, 121212, resp.Reference.Bytes())
var ( var (
feedChunkAddr = swarm.MustParseHexAddress("891a1d1c8436c792d02fc2e8883fef7ab387eaeaacd25aa9f518be7be7856d54") feedChunkAddr = swarm.MustParseHexAddress("891a1d1c8436c792d02fc2e8883fef7ab387eaeaacd25aa9f518be7be7856d54")
......
...@@ -29,9 +29,7 @@ const ( ...@@ -29,9 +29,7 @@ const (
feedMetadataEntryType = "swarm-feed-type" feedMetadataEntryType = "swarm-feed-type"
) )
var ( var errInvalidFeedUpdate = errors.New("invalid feed update")
errInvalidFeedUpdate = errors.New("invalid feed update")
)
type feedReferenceResponse struct { type feedReferenceResponse struct {
Reference swarm.Address `json:"reference"` Reference swarm.Address `json:"reference"`
...@@ -176,12 +174,12 @@ func (s *server) feedPostHandler(w http.ResponseWriter, r *http.Request) { ...@@ -176,12 +174,12 @@ func (s *server) feedPostHandler(w http.ResponseWriter, r *http.Request) {
} }
func parseFeedUpdate(ch swarm.Chunk) (swarm.Address, int64, error) { func parseFeedUpdate(ch swarm.Chunk) (swarm.Address, int64, error) {
sch, err := soc.FromChunk(ch) s, err := soc.FromChunk(ch)
if err != nil { if err != nil {
return swarm.ZeroAddress, 0, fmt.Errorf("soc unmarshal: %w", err) return swarm.ZeroAddress, 0, fmt.Errorf("soc unmarshal: %w", err)
} }
update := sch.Chunk.Data() update := s.WrappedChunk().Data()
// split the timestamp and reference // split the timestamp and reference
// possible values right now: // possible values right now:
// unencrypted ref: span+timestamp+ref => 8+8+32=48 // unencrypted ref: span+timestamp+ref => 8+8+32=48
......
...@@ -16,15 +16,13 @@ import ( ...@@ -16,15 +16,13 @@ import (
"testing" "testing"
"github.com/ethersphere/bee/pkg/api" "github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/cac"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/feeds" "github.com/ethersphere/bee/pkg/feeds"
"github.com/ethersphere/bee/pkg/file/loadsave" "github.com/ethersphere/bee/pkg/file/loadsave"
"github.com/ethersphere/bee/pkg/jsonhttp" "github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest" "github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/logging" "github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/manifest" "github.com/ethersphere/bee/pkg/manifest"
"github.com/ethersphere/bee/pkg/soc" testingsoc "github.com/ethersphere/bee/pkg/soc/testing"
statestore "github.com/ethersphere/bee/pkg/statestore/mock" statestore "github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/storage" "github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/storage/mock" "github.com/ethersphere/bee/pkg/storage/mock"
...@@ -84,7 +82,7 @@ func TestFeed_Get(t *testing.T) { ...@@ -84,7 +82,7 @@ func TestFeed_Get(t *testing.T) {
t.Run("with at", func(t *testing.T) { t.Run("with at", func(t *testing.T) {
var ( var (
timestamp = int64(12121212) timestamp = int64(12121212)
ch, _ = toChunk(uint64(timestamp), expReference.Bytes()) ch = toChunk(t, uint64(timestamp), expReference.Bytes())
look = newMockLookup(12, 0, ch, nil, &id{}, &id{}) look = newMockLookup(12, 0, ch, nil, &id{}, &id{})
factory = newMockFactory(look) factory = newMockFactory(look)
idBytes, _ = (&id{}).MarshalBinary() idBytes, _ = (&id{}).MarshalBinary()
...@@ -115,7 +113,7 @@ func TestFeed_Get(t *testing.T) { ...@@ -115,7 +113,7 @@ func TestFeed_Get(t *testing.T) {
t.Run("latest", func(t *testing.T) { t.Run("latest", func(t *testing.T) {
var ( var (
timestamp = int64(12121212) timestamp = int64(12121212)
ch, _ = toChunk(uint64(timestamp), expReference.Bytes()) ch = toChunk(t, uint64(timestamp), expReference.Bytes())
look = newMockLookup(-1, 2, ch, nil, &id{}, &id{}) look = newMockLookup(-1, 2, ch, nil, &id{}, &id{})
factory = newMockFactory(look) factory = newMockFactory(look)
idBytes, _ = (&id{}).MarshalBinary() idBytes, _ = (&id{}).MarshalBinary()
...@@ -237,36 +235,16 @@ func (l *mockLookup) At(_ context.Context, at, after int64) (swarm.Chunk, feeds. ...@@ -237,36 +235,16 @@ func (l *mockLookup) At(_ context.Context, at, after int64) (swarm.Chunk, feeds.
return nil, nil, nil, errors.New("no feed update found") return nil, nil, nil, errors.New("no feed update found")
} }
func toChunk(at uint64, payload []byte) (swarm.Chunk, error) { func toChunk(t *testing.T, at uint64, payload []byte) swarm.Chunk {
ts := make([]byte, 8) ts := make([]byte, 8)
binary.BigEndian.PutUint64(ts, at) binary.BigEndian.PutUint64(ts, at)
content := append(ts, payload...) content := append(ts, payload...)
ch, err := cac.New(content)
if err != nil {
return nil, err
}
id := make([]byte, soc.IdSize)
privKey, err := crypto.GenerateSecp256k1Key()
if err != nil {
return nil, err
}
signer := crypto.NewDefaultSigner(privKey)
sch := soc.New(id, ch) s := testingsoc.GenerateMockSOC(t, content)
if err != nil { return s.Chunk()
return nil, err
}
err = sch.AddSigner(signer)
if err != nil {
return nil, err
}
return sch.ToChunk()
} }
type id struct { type id struct{}
}
func (i *id) MarshalBinary() ([]byte, error) { func (i *id) MarshalBinary() ([]byte, error) {
return []byte("accd"), nil return []byte("accd"), nil
......
...@@ -88,7 +88,15 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) { ...@@ -88,7 +88,15 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
chunk, err := soc.NewSignedChunk(id, ch, owner, sig) ss, err := soc.NewSigned(id, ch, owner, sig)
if err != nil {
s.logger.Debugf("soc upload: address soc error: %v", err)
s.logger.Error("soc upload: address soc error")
jsonhttp.Unauthorized(w, "invalid address")
return
}
sch, err := ss.Chunk()
if err != nil { if err != nil {
s.logger.Debugf("soc upload: read chunk data error: %v", err) s.logger.Debugf("soc upload: read chunk data error: %v", err)
s.logger.Error("soc upload: read chunk data error") s.logger.Error("soc upload: read chunk data error")
...@@ -96,7 +104,7 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) { ...@@ -96,7 +104,7 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if !soc.Valid(chunk) { if !soc.Valid(sch) {
s.logger.Debugf("soc upload: invalid chunk: %v", err) s.logger.Debugf("soc upload: invalid chunk: %v", err)
s.logger.Error("soc upload: invalid chunk") s.logger.Error("soc upload: invalid chunk")
jsonhttp.Unauthorized(w, "invalid chunk") jsonhttp.Unauthorized(w, "invalid chunk")
...@@ -106,7 +114,7 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) { ...@@ -106,7 +114,7 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
has, err := s.storer.Has(ctx, chunk.Address()) has, err := s.storer.Has(ctx, sch.Address())
if err != nil { if err != nil {
s.logger.Debugf("soc upload: store has: %v", err) s.logger.Debugf("soc upload: store has: %v", err)
s.logger.Error("soc upload: store has") s.logger.Error("soc upload: store has")
...@@ -119,7 +127,7 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) { ...@@ -119,7 +127,7 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = s.storer.Put(ctx, requestModePut(r), chunk) _, err = s.storer.Put(ctx, requestModePut(r), sch)
if err != nil { if err != nil {
s.logger.Debugf("soc upload: chunk write error: %v", err) s.logger.Debugf("soc upload: chunk write error: %v", err)
s.logger.Error("soc upload: chunk write error") s.logger.Error("soc upload: chunk write error")
...@@ -127,5 +135,5 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) { ...@@ -127,5 +135,5 @@ func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonhttp.Created(w, chunkAddressResponse{Reference: chunk.Address()}) jsonhttp.Created(w, chunkAddressResponse{Reference: sch.Address()})
} }
...@@ -6,7 +6,6 @@ package api_test ...@@ -6,7 +6,6 @@ package api_test
import ( import (
"bytes" "bytes"
"encoding/binary"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
...@@ -14,19 +13,19 @@ import ( ...@@ -14,19 +13,19 @@ import (
"testing" "testing"
"github.com/ethersphere/bee/pkg/api" "github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/jsonhttp" "github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest" "github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/logging" "github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/soc" "github.com/ethersphere/bee/pkg/soc"
testingsoc "github.com/ethersphere/bee/pkg/soc/testing"
statestore "github.com/ethersphere/bee/pkg/statestore/mock" statestore "github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/storage/mock" "github.com/ethersphere/bee/pkg/storage/mock"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/ethersphere/bee/pkg/tags" "github.com/ethersphere/bee/pkg/tags"
) )
func TestSoc(t *testing.T) { func TestSOC(t *testing.T) {
var ( var (
testData = []byte("foo")
socResource = func(owner, id, sig string) string { return fmt.Sprintf("/soc/%s/%s?sig=%s", owner, id, sig) } socResource = func(owner, id, sig string) string { return fmt.Sprintf("/soc/%s/%s?sig=%s", owner, id, sig) }
mockStatestore = statestore.NewStateStore() mockStatestore = statestore.NewStateStore()
logger = logging.New(ioutil.Discard, 0) logger = logging.New(ioutil.Discard, 0)
...@@ -37,7 +36,6 @@ func TestSoc(t *testing.T) { ...@@ -37,7 +36,6 @@ func TestSoc(t *testing.T) {
Tags: tag, Tags: tag,
}) })
) )
t.Run("cmpty data", func(t *testing.T) { t.Run("cmpty data", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, socResource("8d3766440f0d7b949a5e32995d09619a7f86e632", "bb", "cc"), http.StatusBadRequest, jsonhttptest.Request(t, client, http.MethodPost, socResource("8d3766440f0d7b949a5e32995d09619a7f86e632", "bb", "cc"), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
...@@ -75,16 +73,16 @@ func TestSoc(t *testing.T) { ...@@ -75,16 +73,16 @@ func TestSoc(t *testing.T) {
}) })
t.Run("signature invalid", func(t *testing.T) { t.Run("signature invalid", func(t *testing.T) {
s, owner, payload := mockSoc(t) s := testingsoc.GenerateMockSOC(t, testData)
id := make([]byte, soc.IdSize)
// modify the sign // modify the sign
sig := s.Signature() sig := make([]byte, soc.SignatureSize)
copy(sig, s.Signature)
sig[12] = 0x98 sig[12] = 0x98
sig[10] = 0x12 sig[10] = 0x12
jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(owner), hex.EncodeToString(id), hex.EncodeToString(sig)), http.StatusUnauthorized, jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(s.Owner), hex.EncodeToString(s.ID), hex.EncodeToString(sig)), http.StatusUnauthorized,
jsonhttptest.WithRequestBody(bytes.NewReader(payload)), jsonhttptest.WithRequestBody(bytes.NewReader(s.WrappedChunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: "invalid chunk", Message: "invalid chunk",
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
...@@ -93,55 +91,39 @@ func TestSoc(t *testing.T) { ...@@ -93,55 +91,39 @@ func TestSoc(t *testing.T) {
}) })
t.Run("ok", func(t *testing.T) { t.Run("ok", func(t *testing.T) {
s, owner, payload := mockSoc(t) s := testingsoc.GenerateMockSOC(t, testData)
id := make([]byte, soc.IdSize)
sig := s.Signature() jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(s.Owner), hex.EncodeToString(s.ID), hex.EncodeToString(s.Signature)), http.StatusCreated,
addr, err := s.Address() jsonhttptest.WithRequestBody(bytes.NewReader(s.WrappedChunk.Data())),
if err != nil {
t.Fatal(err)
}
jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(owner), hex.EncodeToString(id), hex.EncodeToString(sig)), http.StatusCreated,
jsonhttptest.WithRequestBody(bytes.NewReader(payload)),
jsonhttptest.WithExpectedJSONResponse(api.SocPostResponse{ jsonhttptest.WithExpectedJSONResponse(api.SocPostResponse{
Reference: addr, Reference: s.Address(),
}), }),
) )
// try to fetch the same chunk // try to fetch the same chunk
rsrc := fmt.Sprintf("/chunks/" + addr.String()) rsrc := fmt.Sprintf("/chunks/" + s.Address().String())
resp := request(t, client, http.MethodGet, rsrc, nil, http.StatusOK) resp := request(t, client, http.MethodGet, rsrc, nil, http.StatusOK)
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ch, err := s.ToChunk()
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ch.Data(), data) { if !bytes.Equal(s.Chunk().Data(), data) {
t.Fatal("data retrieved doesnt match uploaded content") t.Fatal("data retrieved doesn't match uploaded content")
} }
}) })
t.Run("already exists", func(t *testing.T) { t.Run("already exists", func(t *testing.T) {
s, owner, payload := mockSoc(t) s := testingsoc.GenerateMockSOC(t, testData)
id := make([]byte, soc.IdSize)
sig := s.Signature() jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(s.Owner), hex.EncodeToString(s.ID), hex.EncodeToString(s.Signature)), http.StatusCreated,
addr, err := s.Address() jsonhttptest.WithRequestBody(bytes.NewReader(s.WrappedChunk.Data())),
if err != nil {
t.Fatal(err)
}
jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(owner), hex.EncodeToString(id), hex.EncodeToString(sig)), http.StatusCreated,
jsonhttptest.WithRequestBody(bytes.NewReader(payload)),
jsonhttptest.WithExpectedJSONResponse(api.SocPostResponse{ jsonhttptest.WithExpectedJSONResponse(api.SocPostResponse{
Reference: addr, Reference: s.Address(),
}), }),
) )
jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(owner), hex.EncodeToString(id), hex.EncodeToString(sig)), http.StatusConflict, jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(s.Owner), hex.EncodeToString(s.ID), hex.EncodeToString(s.Signature)), http.StatusConflict,
jsonhttptest.WithRequestBody(bytes.NewReader(payload)), jsonhttptest.WithRequestBody(bytes.NewReader(s.WrappedChunk.Data())),
jsonhttptest.WithExpectedJSONResponse( jsonhttptest.WithExpectedJSONResponse(
jsonhttp.StatusResponse{ jsonhttp.StatusResponse{
Message: "chunk already exists", Message: "chunk already exists",
...@@ -149,38 +131,4 @@ func TestSoc(t *testing.T) { ...@@ -149,38 +131,4 @@ func TestSoc(t *testing.T) {
}), }),
) )
}) })
}
// returns a valid, mocked SOC
func mockSoc(t *testing.T) (*soc.Soc, []byte, []byte) {
// create a valid soc
id := make([]byte, soc.IdSize)
privKey, err := crypto.GenerateSecp256k1Key()
if err != nil {
t.Fatal(err)
}
signer := crypto.NewDefaultSigner(privKey)
bmtHashOfFoo := "2387e8e7d8a48c2a9339c97c1dc3461a9a7aa07e994c5cb8b38fd7c1b3e6ea48"
address := swarm.MustParseHexAddress(bmtHashOfFoo)
foo := "foo"
fooLength := len(foo)
fooBytes := make([]byte, 8+fooLength)
binary.LittleEndian.PutUint64(fooBytes, uint64(fooLength))
copy(fooBytes[8:], foo)
ch := swarm.NewChunk(address, fooBytes)
sch := soc.New(id, ch)
if err != nil {
t.Fatal(err)
}
err = sch.AddSigner(signer)
if err != nil {
t.Fatal(err)
}
_, _ = sch.ToChunk()
return sch, sch.OwnerAddress(), ch.Data()
} }
...@@ -112,10 +112,16 @@ func NewUpdate(f *Feed, idx Index, timestamp int64, payload []byte, sig []byte) ...@@ -112,10 +112,16 @@ func NewUpdate(f *Feed, idx Index, timestamp int64, payload []byte, sig []byte)
return nil, fmt.Errorf("toChunk: %w", err) return nil, fmt.Errorf("toChunk: %w", err)
} }
ch, err := soc.NewSignedChunk(id, cac, f.Owner.Bytes(), sig) ss, err := soc.NewSigned(id, cac, f.Owner.Bytes(), sig)
if err != nil {
return nil, fmt.Errorf("new signed soc: %w", err)
}
ch, err := ss.Chunk()
if err != nil { if err != nil {
return nil, fmt.Errorf("new chunk: %w", err) return nil, fmt.Errorf("new chunk: %w", err)
} }
if !soc.Valid(ch) { if !soc.Valid(ch) {
return nil, storage.ErrInvalidChunk return nil, storage.ErrInvalidChunk
} }
...@@ -135,7 +141,6 @@ func Id(topic []byte, index Index) ([]byte, error) { ...@@ -135,7 +141,6 @@ func Id(topic []byte, index Index) ([]byte, error) {
} }
i := &id{topic, indexBytes} i := &id{topic, indexBytes}
return i.MarshalBinary() return i.MarshalBinary()
} }
// Address calculates the soc address of a feed update // Address calculates the soc address of a feed update
...@@ -145,9 +150,5 @@ func (u *Update) Address() (swarm.Address, error) { ...@@ -145,9 +150,5 @@ func (u *Update) Address() (swarm.Address, error) {
if err != nil { if err != nil {
return addr, err return addr, err
} }
owner, err := soc.NewOwner(u.Owner[:]) return soc.CreateAddress(i, u.Owner[:])
if err != nil {
return addr, err
}
return soc.CreateAddress(i, owner)
} }
...@@ -55,7 +55,7 @@ func FromChunk(ch swarm.Chunk) (uint64, []byte, error) { ...@@ -55,7 +55,7 @@ func FromChunk(ch swarm.Chunk) (uint64, []byte, error) {
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
cac := s.Chunk cac := s.WrappedChunk()
if len(cac.Data()) < 16 { if len(cac.Data()) < 16 {
return 0, nil, fmt.Errorf("feed update payload too short") return 0, nil, fmt.Errorf("feed update payload too short")
} }
......
...@@ -48,7 +48,8 @@ func (u *Putter) Put(ctx context.Context, i Index, at int64, payload []byte) err ...@@ -48,7 +48,8 @@ func (u *Putter) Put(ctx context.Context, i Index, at int64, payload []byte) err
if err != nil { if err != nil {
return err return err
} }
ch, err := soc.NewChunk(id, cac, u.signer) s := soc.New(id, cac)
ch, err := s.Sign(u.signer)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -5,6 +5,22 @@ ...@@ -5,6 +5,22 @@
package soc package soc
var ( var (
ToSignDigest = toSignDigest ErrInvalidAddress = errInvalidAddress
RecoverAddress = recoverAddress Hash = hash
RecoverAddress = recoverAddress
) )
// Signature returns the SOC signature.
func (s *SOC) Signature() []byte {
return s.signature
}
// OwnerAddress returns the ethereum address of the SOC owner.
func (s *SOC) OwnerAddress() []byte {
return s.owner
}
// ID returns the SOC id.
func (s *SOC) ID() []byte {
return s.id
}
...@@ -2,14 +2,13 @@ ...@@ -2,14 +2,13 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package soc provides the single-owner chunk implemenation // Package soc provides the single-owner chunk implementation
// and validator. // and validator.
package soc package soc
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"github.com/ethersphere/bee/pkg/cac" "github.com/ethersphere/bee/pkg/cac"
"github.com/ethersphere/bee/pkg/crypto" "github.com/ethersphere/bee/pkg/crypto"
...@@ -22,130 +21,118 @@ const ( ...@@ -22,130 +21,118 @@ const (
minChunkSize = IdSize + SignatureSize + swarm.SpanSize minChunkSize = IdSize + SignatureSize + swarm.SpanSize
) )
// Id is a soc identifier var (
type Id []byte errInvalidAddress = errors.New("soc: invalid address")
errWrongChunkSize = errors.New("soc: chunk length is less than minimum")
// Owner is a wrapper that enforces valid length address of soc owner. )
type Owner struct {
address []byte
}
// NewOwner creates a new Owner. // ID is a SOC identifier
func NewOwner(address []byte) (*Owner, error) { type ID []byte
if len(address) != crypto.AddressSize {
return nil, fmt.Errorf("invalid address %x", address)
}
return &Owner{
address: address,
}, nil
}
// Soc wraps a single soc. // SOC wraps a content-addressed chunk.
type Soc struct { type SOC struct {
id Id id ID
owner []byte // owner is the address in bytes of SOC owner.
signature []byte signature []byte
signer crypto.Signer chunk swarm.Chunk // wrapped chunk.
owner *Owner
Chunk swarm.Chunk
} }
// NewChunk is a convenience function to create a single-owner chunk ready to be sent // New creates a new SOC representation from arbitrary id and
// on the network. // a content-addressed chunk.
func NewChunk(id Id, ch swarm.Chunk, signer crypto.Signer) (swarm.Chunk, error) { func New(id ID, ch swarm.Chunk) *SOC {
s := New(id, ch) return &SOC{
err := s.AddSigner(signer) id: id,
if err != nil { chunk: ch,
return nil, err
} }
return s.ToChunk()
} }
// NewChunk is a convenience function to create a single-owner chunk ready to be sent // NewSigned creates a single-owner chunk based on already signed data.
// on the network. func NewSigned(id ID, ch swarm.Chunk, owner, sig []byte) (*SOC, error) {
func NewSignedChunk(id Id, ch swarm.Chunk, owner, sig []byte) (swarm.Chunk, error) {
s := New(id, ch) s := New(id, ch)
if len(owner) != crypto.AddressSize {
return nil, errInvalidAddress
}
s.owner = owner
s.signature = sig s.signature = sig
o, err := NewOwner(owner) return s, nil
if err != nil { }
return nil, err
// address returns the SOC chunk address.
func (s *SOC) address() (swarm.Address, error) {
if len(s.owner) != crypto.AddressSize {
return swarm.ZeroAddress, errInvalidAddress
} }
s.owner = o return CreateAddress(s.id, s.owner)
}
// create chunk // WrappedChunk returns the chunk wrapped by the SOC.
socAddress, err := s.Address() func (s *SOC) WrappedChunk() swarm.Chunk {
return s.chunk
}
// Chunk returns the SOC chunk.
func (s *SOC) Chunk() (swarm.Chunk, error) {
socAddress, err := s.address()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return swarm.NewChunk(socAddress, s.toBytes()), nil return swarm.NewChunk(socAddress, s.toBytes()), nil
} }
// New creates a new Soc representation from arbitrary soc id and // toBytes is a helper function to convert the SOC data to bytes.
// a content-addressed chunk. func (s *SOC) toBytes() []byte {
// buf := bytes.NewBuffer(nil)
// By default the span of the soc data is set to the length buf.Write(s.id)
// of the payload. buf.Write(s.signature)
func New(id Id, ch swarm.Chunk) *Soc { buf.Write(s.chunk.Data())
return &Soc{ return buf.Bytes()
id: id,
Chunk: ch,
}
}
// WithOwnerAddress provides the possibility of setting the ethereum
// address for the owner of an soc in the absence of a signer.
func (s *Soc) WithOwnerAddress(ownerAddress *Owner) *Soc {
s.owner = ownerAddress
return s
} }
// AddSigner currently sets a single signer for the soc. // Sign signs a SOC using the given signer.
// // It returns a signed SOC chunk ready for submission to the network.
// This method will overwrite any value set with WithOwnerAddress with func (s *SOC) Sign(signer crypto.Signer) (swarm.Chunk, error) {
// the address derived from the given signer. // create owner
func (s *Soc) AddSigner(signer crypto.Signer) error {
publicKey, err := signer.PublicKey() publicKey, err := signer.PublicKey()
if err != nil { if err != nil {
return err return nil, err
} }
ownerAddressBytes, err := crypto.NewEthereumAddress(*publicKey) ownerAddressBytes, err := crypto.NewEthereumAddress(*publicKey)
if err != nil { if err != nil {
return err return nil, err
} }
ownerAddress, err := NewOwner(ownerAddressBytes) if len(ownerAddressBytes) != crypto.AddressSize {
if err != nil { return nil, errInvalidAddress
return err
} }
s.signer = signer s.owner = ownerAddressBytes
s.owner = ownerAddress
return nil
}
// OwnerAddress returns the ethereum address of the signer of the Chunk. // generate the data to sign
func (s *Soc) OwnerAddress() []byte { toSignBytes, err := hash(s.id, s.chunk.Address().Bytes())
return s.owner.address if err != nil {
} return nil, err
}
// Address returns the soc Chunk address. // sign the chunk
func (s *Soc) Address() (swarm.Address, error) { signature, err := signer.Sign(toSignBytes)
return CreateAddress(s.id, s.owner) if err != nil {
} return nil, err
}
s.signature = signature
func (s *Soc) Signature() []byte { return s.Chunk()
return s.signature
} }
// FromChunk recreates an Soc representation from swarm.Chunk data. // FromChunk recreates a SOC representation from swarm.Chunk data.
func FromChunk(sch swarm.Chunk) (*Soc, error) { func FromChunk(sch swarm.Chunk) (*SOC, error) {
chunkData := sch.Data() chunkData := sch.Data()
if len(chunkData) < minChunkSize { if len(chunkData) < minChunkSize {
return nil, errors.New("less than minimum length") return nil, errWrongChunkSize
} }
// add all the data fields // add all the data fields to the SOC
s := &Soc{} s := &SOC{}
cursor := 0 cursor := 0
s.id = chunkData[cursor : cursor+IdSize] s.id = chunkData[cursor:IdSize]
cursor += IdSize cursor += IdSize
s.signature = chunkData[cursor : cursor+SignatureSize] s.signature = chunkData[cursor : cursor+SignatureSize]
...@@ -156,94 +143,48 @@ func FromChunk(sch swarm.Chunk) (*Soc, error) { ...@@ -156,94 +143,48 @@ func FromChunk(sch swarm.Chunk) (*Soc, error) {
return nil, err return nil, err
} }
toSignBytes, err := toSignDigest(s.id, ch.Address().Bytes()) toSignBytes, err := hash(s.id, ch.Address().Bytes())
if err != nil { if err != nil {
return nil, err return nil, err
} }
// recover owner information // recover owner information
recoveredEthereumAddress, err := recoverAddress(s.signature, toSignBytes) recoveredOwnerAddress, err := recoverAddress(s.signature, toSignBytes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
owner, err := NewOwner(recoveredEthereumAddress) if len(recoveredOwnerAddress) != crypto.AddressSize {
if err != nil { return nil, errInvalidAddress
return nil, err
} }
s.owner = owner s.owner = recoveredOwnerAddress
s.Chunk = ch s.chunk = ch
return s, nil return s, nil
} }
// ToChunk generates a signed chunk payload ready for submission to the swarm network. // CreateAddress creates a new SOC address from the id and
// // the ethereum address of the owner.
// The method will fail if no signer has been added. func CreateAddress(id ID, owner []byte) (swarm.Address, error) {
func (s *Soc) ToChunk() (swarm.Chunk, error) { sum, err := hash(id, owner)
var err error
if s.signer == nil {
return nil, errors.New("signer missing")
}
// generate the data to sign
toSignBytes, err := toSignDigest(s.id, s.Chunk.Address().Bytes())
if err != nil {
return nil, err
}
// sign the chunk
signature, err := s.signer.Sign(toSignBytes)
if err != nil { if err != nil {
return nil, err return swarm.ZeroAddress, err
}
s.signature = signature
// create chunk
socAddress, err := s.Address()
if err != nil {
return nil, err
} }
return swarm.NewChunk(socAddress, s.toBytes()), nil return swarm.NewAddress(sum), nil
}
func (s *Soc) toBytes() []byte {
buf := bytes.NewBuffer(nil)
buf.Write(s.id)
buf.Write(s.signature)
buf.Write(s.Chunk.Data())
return buf.Bytes()
} }
// toSignDigest creates a digest suitable for signing to represent the soc. // hash hashes the given values in order.
func toSignDigest(id Id, sum []byte) ([]byte, error) { func hash(values ...[]byte) ([]byte, error) {
h := swarm.NewHasher() h := swarm.NewHasher()
_, err := h.Write(id) for _, v := range values {
if err != nil { _, err := h.Write(v)
return nil, err if err != nil {
} return nil, err
_, err = h.Write(sum) }
if err != nil {
return nil, err
} }
return h.Sum(nil), nil return h.Sum(nil), nil
} }
// CreateAddress creates a new soc address from the soc id and the ethereum address of the signer // recoverAddress returns the ethereum address of the owner of a SOC.
func CreateAddress(id Id, owner *Owner) (swarm.Address, error) {
h := swarm.NewHasher()
_, err := h.Write(id)
if err != nil {
return swarm.ZeroAddress, err
}
_, err = h.Write(owner.address)
if err != nil {
return swarm.ZeroAddress, err
}
sum := h.Sum(nil)
return swarm.NewAddress(sum), nil
}
// recoverOwner returns the ethereum address of the owner of an soc.
func recoverAddress(signature, digest []byte) ([]byte, error) { func recoverAddress(signature, digest []byte) ([]byte, error) {
recoveredPublicKey, err := crypto.Recover(signature, digest) recoveredPublicKey, err := crypto.Recover(signature, digest)
if err != nil { if err != nil {
......
This diff is collapsed.
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing
import (
"testing"
"github.com/ethersphere/bee/pkg/cac"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/soc"
"github.com/ethersphere/bee/pkg/swarm"
)
// MockSOC defines a mocked SOC with exported fields for easy testing.
type MockSOC struct {
ID soc.ID
Owner []byte
Signature []byte
WrappedChunk swarm.Chunk
}
// Address returns the SOC address of the mocked SOC.
func (ms MockSOC) Address() swarm.Address {
addr, _ := soc.CreateAddress(ms.ID, ms.Owner)
return addr
}
// Chunk returns the SOC chunk of the mocked SOC.
func (ms MockSOC) Chunk() swarm.Chunk {
return swarm.NewChunk(ms.Address(), append(ms.ID, append(ms.Signature, ms.WrappedChunk.Data()...)...))
}
// GenerateMockSOC generates a valid mocked SOC from given data.
func GenerateMockSOC(t *testing.T, data []byte) *MockSOC {
t.Helper()
privKey, err := crypto.GenerateSecp256k1Key()
if err != nil {
t.Fatal(err)
}
signer := crypto.NewDefaultSigner(privKey)
owner, err := signer.EthereumAddress()
if err != nil {
t.Fatal(err)
}
ch, err := cac.New(data)
if err != nil {
t.Fatal(err)
}
id := make([]byte, soc.IdSize)
hasher := swarm.NewHasher()
_, err = hasher.Write(append(id, ch.Address().Bytes()...))
if err != nil {
t.Fatal(err)
}
signature, err := signer.Sign(hasher.Sum(nil))
if err != nil {
t.Fatal(err)
}
return &MockSOC{
ID: id,
Owner: owner.Bytes(),
Signature: signature,
WrappedChunk: ch,
}
}
...@@ -15,7 +15,7 @@ func Valid(ch swarm.Chunk) bool { ...@@ -15,7 +15,7 @@ func Valid(ch swarm.Chunk) bool {
return false return false
} }
address, err := s.Address() address, err := s.address()
if err != nil { if err != nil {
return false return false
} }
......
...@@ -5,57 +5,110 @@ ...@@ -5,57 +5,110 @@
package soc_test package soc_test
import ( import (
"encoding/binary" "strings"
"testing" "testing"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/soc" "github.com/ethersphere/bee/pkg/soc"
"github.com/ethersphere/bee/pkg/swarm" "github.com/ethersphere/bee/pkg/swarm"
) )
// TestValidator verifies that the validator can detect both // TestValid verifies that the validator can detect
// valid soc chunks, as well as chunks with invalid data and invalid // valid soc chunks.
// address. func TestValid(t *testing.T) {
func TestValidator(t *testing.T) { socAddress := swarm.MustParseHexAddress("9d453ebb73b2fedaaf44ceddcf7a0aa37f3e3d6453fea5841c31f0ea6d61dc85")
id := make([]byte, soc.IdSize)
privKey, err := crypto.GenerateSecp256k1Key() // signed soc chunk of:
if err != nil { // id: 0
t.Fatal(err) // wrapped chunk of: `foo`
} // owner: 0x8d3766440f0d7b949a5e32995d09619a7f86e632
signer := crypto.NewDefaultSigner(privKey) 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})
bmtHashOfFoo := "2387e8e7d8a48c2a9339c97c1dc3461a9a7aa07e994c5cb8b38fd7c1b3e6ea48"
address := swarm.MustParseHexAddress(bmtHashOfFoo)
foo := "foo"
fooLength := len(foo)
fooBytes := make([]byte, 8+fooLength)
binary.LittleEndian.PutUint64(fooBytes, uint64(fooLength))
copy(fooBytes[8:], foo)
ch := swarm.NewChunk(address, fooBytes)
sch, err := soc.NewChunk(id, ch, signer)
if err != nil {
t.Fatal(err)
}
// check valid chunk // check valid chunk
if !soc.Valid(sch) { if !soc.Valid(sch) {
t.Fatal("valid chunk evaluates to invalid") t.Fatal("valid chunk evaluates to invalid")
} }
}
// check invalid data // TestInvalid verifies that the validator can detect chunks
sch.Data()[0] = 0x01 // with invalid data and invalid address.
if soc.Valid(sch) { func TestInvalid(t *testing.T) {
t.Fatal("chunk with invalid data evaluates to valid") socAddress := swarm.MustParseHexAddress("9d453ebb73b2fedaaf44ceddcf7a0aa37f3e3d6453fea5841c31f0ea6d61dc85")
}
// signed soc chunk of:
// id: 0
// wrapped chunk of: `foo`
// owner: 0x8d3766440f0d7b949a5e32995d09619a7f86e632
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})
// check invalid address for _, c := range []struct {
sch.Data()[0] = 0x00 name string
wrongAddressBytes := sch.Address().Bytes() chunk func() swarm.Chunk
wrongAddressBytes[0] = 255 - wrongAddressBytes[0] }{
wrongAddress := swarm.NewAddress(wrongAddressBytes) {
sch = swarm.NewChunk(wrongAddress, sch.Data()) name: "wrong soc address",
if soc.Valid(sch) { chunk: func() swarm.Chunk {
t.Fatal("chunk with invalid address evaluates to valid") wrongAddressBytes := sch.Address().Bytes()
wrongAddressBytes[0] = 255 - wrongAddressBytes[0]
wrongAddress := swarm.NewAddress(wrongAddressBytes)
return swarm.NewChunk(wrongAddress, sch.Data())
},
},
{
name: "invalid data",
chunk: func() swarm.Chunk {
data := make([]byte, len(sch.Data()))
copy(data, sch.Data())
cursor := soc.IdSize + soc.SignatureSize
chunkData := data[cursor:]
chunkData[0] = 0x01
return swarm.NewChunk(socAddress, data)
},
},
{
name: "invalid id",
chunk: func() swarm.Chunk {
data := make([]byte, len(sch.Data()))
copy(data, sch.Data())
id := data[:soc.IdSize]
id[0] = 0x01
return swarm.NewChunk(socAddress, data)
},
},
{
name: "invalid signature",
chunk: func() swarm.Chunk {
data := make([]byte, len(sch.Data()))
copy(data, sch.Data())
// modify signature
cursor := soc.IdSize + soc.SignatureSize
sig := data[soc.IdSize:cursor]
sig[0] = 0x01
return swarm.NewChunk(socAddress, data)
},
},
{
name: "nil data",
chunk: func() swarm.Chunk {
return swarm.NewChunk(socAddress, nil)
},
},
{
name: "small data",
chunk: func() swarm.Chunk {
return swarm.NewChunk(socAddress, []byte("small"))
},
},
{
name: "large data",
chunk: func() swarm.Chunk {
return swarm.NewChunk(socAddress, []byte(strings.Repeat("a", swarm.ChunkSize+swarm.SpanSize+1)))
},
},
} {
t.Run(c.name, func(t *testing.T) {
if soc.Valid(c.chunk()) {
t.Fatal("chunk with invalid data evaluates to valid")
}
})
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment