diff --git a/swarm/storage/resource.go b/swarm/storage/resource.go index 8884be09b3..73efb08b5f 100644 --- a/swarm/storage/resource.go +++ b/swarm/storage/resource.go @@ -532,7 +532,16 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32, namelength := int(headerlength) - cursor + 4 name = string(chunkdata[cursor : cursor+namelength]) cursor += namelength - intdatalength := int(datalength) + var intdatalength int + if datalength == 0 { + intdatalength = isMultihash(chunkdata[cursor:]) + multihashboundary := cursor + intdatalength + if len(chunkdata) != multihashboundary && len(chunkdata) < multihashboundary+signatureLength { + return nil, 0, 0, "", nil, errors.New("Corrupt multihash data") + } + } else { + intdatalength = int(datalength) + } data = make([]byte, intdatalength) copy(data, chunkdata[cursor:cursor+intdatalength]) @@ -553,7 +562,19 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32, // It is the caller's responsibility to make sure that this data is not stale. // // A resource update cannot span chunks, and thus has max length 4096 + +func (self *ResourceHandler) UpdateMultihash(ctx context.Context, name string, data []byte) (Key, error) { + if isMultihash(data) == 0 { + return nil, errors.New("Invalid multihash") + } + return self.update(ctx, name, data, true) +} + func (self *ResourceHandler) Update(ctx context.Context, name string, data []byte) (Key, error) { + return self.update(ctx, name, data, false) +} + +func (self *ResourceHandler) update(ctx context.Context, name string, data []byte, multihash bool) (Key, error) { var signaturelength int if self.validator != nil { @@ -618,7 +639,11 @@ func (self *ResourceHandler) Update(ctx context.Context, name string, data []byt } } - chunk := newUpdateChunk(key, signature, nextperiod, version, name, data) + var datalength int + if !multihash { + datalength = len(data) + } + chunk := newUpdateChunk(key, signature, nextperiod, version, name, data, datalength) // send the chunk self.Put(chunk) @@ -703,7 +728,7 @@ func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Ad } // create an update chunk -func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32, name string, data []byte) *Chunk { +func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32, name string, data []byte, datalength int) *Chunk { // no signatures if no validator var signaturelength int @@ -714,11 +739,9 @@ func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32 // prepend version and period to allow reverse lookups headerlength := len(name) + 4 + 4 - // also prepend datalength - datalength := len(data) - + actualdatalength := len(data) chunk := NewChunk(key, nil) - chunk.SData = make([]byte, 4+signaturelength+headerlength+datalength) // initial 4 are uint16 length descriptors for headerlength and datalength + chunk.SData = make([]byte, 4+signaturelength+headerlength+actualdatalength) // initial 4 are uint16 length descriptors for headerlength and datalength // data header length does NOT include the header length prefix bytes themselves cursor := 0 @@ -830,3 +853,31 @@ func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash { hasher.Write(data) return common.BytesToHash(hasher.Sum(nil)) } + +// if first byte is the start of a multihash this function will try to parse it +// if successful it returns the length of multihash data, 0 otherwise +func isMultihash(data []byte) int { + cursor := 0 + hashtype, c := binary.Uvarint(data) + log.Trace("ismultihash", "hashtype", hashtype, "c", c) + if c == 0 { + log.Debug("Corrupt multihash data, hashtype is unreadable") + return 0 + } + cursor += c + hashlength, c := binary.Uvarint(data[cursor:]) + log.Trace("ismultihash", "hashlength", hashlength, "c", c) + if c == 0 { + log.Debug("Corrupt multihash data, hashlength is unreadable") + return 0 + } + cursor += c + // we cheekily assume hashlength < maxint + inthashlength := int(hashlength) + log.Trace("ismultihash", "datalen", len(data), "hashlength", inthashlength, "cursor", c) + if len(data[cursor:]) < inthashlength { + log.Debug("Corrupt multihash data, hash does not align with data boundary") + return 0 + } + return cursor + inthashlength +} diff --git a/swarm/storage/resource_test.go b/swarm/storage/resource_test.go index 376b8397ae..ef03154f06 100644 --- a/swarm/storage/resource_test.go +++ b/swarm/storage/resource_test.go @@ -15,6 +15,8 @@ import ( "testing" "time" + "github.com/multiformats/go-multihash" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" @@ -107,7 +109,7 @@ func TestResourceReverse(t *testing.T) { t.Fatal(err) } - chunk := newUpdateChunk(key, &sig, period, version, safeName, data) + chunk := newUpdateChunk(key, &sig, period, version, safeName, data, len(data)) // check that we can recover the owner account from the update chunk's signature checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData) @@ -456,3 +458,99 @@ func (self *testValidator) checkAccess(name string, address common.Address) (boo func (self *testValidator) nameHash(name string) common.Hash { return self.hashFunc(name) } + +func TestResourceMultihash(t *testing.T) { + + // signer containing private key + // signer, err := newTestSigner() + // if err != nil { + // t.Fatal(err) + // } + + // make fake backend, set up rpc and create resourcehandler + backend := &fakeBackend{ + blocknumber: int64(startBlock), + } + + // set up rpc and create resourcehandler + rh, _, _, teardownTest, err := setupTest(backend, nil) + if err != nil { + t.Fatal(err) + } + defer teardownTest() + + // create a new resource + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + _, err = rh.NewResource(ctx, safeName, resourceFrequency) + if err != nil { + t.Fatal(err) + } + + // we're naïvely assuming keccak256 for swarm hashes + // if it ever changes this test should also change + swarmhashbytes := rh.nameHash("foo") + swarmhashmulti, err := multihash.Encode(swarmhashbytes.Bytes(), multihash.KECCAK_256) + if err != nil { + t.Fatal(err) + } + swarmhashkey, err := rh.UpdateMultihash(ctx, safeName, swarmhashmulti) + if err != nil { + t.Fatal(err) + } + + sha1bytes := make([]byte, multihash.DefaultLengths[multihash.SHA1]) + sha1multi, err := multihash.Encode(sha1bytes, multihash.SHA1) + if err != nil { + t.Fatal(err) + } + sha1key, err := rh.UpdateMultihash(ctx, safeName, sha1multi) + if err != nil { + t.Fatal(err) + } + + // invalid multihashes + _, err = rh.UpdateMultihash(ctx, safeName, swarmhashmulti[1:]) + if err == nil { + t.Fatalf("Expected update to fail with first byte skipped") + } + _, err = rh.UpdateMultihash(ctx, safeName, swarmhashmulti[:len(swarmhashmulti)-2]) + if err == nil { + t.Fatalf("Expected update to fail with last byte skipped") + } + + swarmhashchunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(swarmhashkey) + if err != nil { + t.Fatal(err) + } + _, _, _, _, data, err := rh.parseUpdate(swarmhashchunk.SData) + if err != nil { + t.Fatal(err) + } + swarmhashdecode, err := multihash.Decode(data) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(swarmhashdecode.Digest, swarmhashbytes.Bytes()) { + t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", swarmhashdecode.Digest, swarmhashbytes.Bytes()) + } + sha1chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(sha1key) + if err != nil { + t.Fatal(err) + } + _, _, _, _, data, err = rh.parseUpdate(sha1chunk.SData) + if err != nil { + t.Fatal(err) + } + sha1decode, err := multihash.Decode(data) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sha1decode.Digest, sha1bytes) { + t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", sha1decode.Digest, sha1bytes) + } + + // test with signed data + // rh2, err := NewResourceHandler(datadir, &testCloudStore{}, rh.ethClient, Signer) + // swarmhashsignedkey, err := rh2.UpdateMultihash(ctx, safeName, swarmhashmulti) +}