mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
swarm/storage: External signing, chunk data verification
This commit is contained in:
parent
c7ce92fe46
commit
56c59d57b1
3 changed files with 138 additions and 77 deletions
|
|
@ -93,7 +93,7 @@ type resource struct {
|
||||||
// treated as a resource update chunk.
|
// treated as a resource update chunk.
|
||||||
|
|
||||||
type ResourceValidator interface {
|
type ResourceValidator interface {
|
||||||
isOwner(string) (bool, error)
|
isOwner(string, common.Address) (bool, error)
|
||||||
nameHash(string) common.Hash
|
nameHash(string) common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +105,6 @@ type ResourceHandler struct {
|
||||||
hashLock sync.Mutex
|
hashLock sync.Mutex
|
||||||
resourceLock sync.RWMutex
|
resourceLock sync.RWMutex
|
||||||
hasher SwarmHash
|
hasher SwarmHash
|
||||||
privKey *ecdsa.PrivateKey
|
|
||||||
maxChunkData int64
|
maxChunkData int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +126,6 @@ func NewResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore Cl
|
||||||
rpcClient: rpcClient,
|
rpcClient: rpcClient,
|
||||||
resources: make(map[string]*resource),
|
resources: make(map[string]*resource),
|
||||||
hasher: hasher(),
|
hasher: hasher(),
|
||||||
privKey: privKey,
|
|
||||||
maxChunkData: DefaultBranches * int64(hasher().Size()),
|
maxChunkData: DefaultBranches * int64(hasher().Size()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,9 +185,13 @@ func NewResource(name string, startBlock uint64, frequency uint64, nameHashFunc
|
||||||
// Creates a new root entry for a mutable resource identified by `name` with the specified `frequency`.
|
// Creates a new root entry for a mutable resource identified by `name` with the specified `frequency`.
|
||||||
//
|
//
|
||||||
// The start block of the resource update will be the actual current block height of the connected network.
|
// The start block of the resource update will be the actual current block height of the connected network.
|
||||||
func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resource, error) {
|
func (self *ResourceHandler) NewResource(name string, frequency uint64, signature [signatureLength]byte) (*resource, error) {
|
||||||
|
|
||||||
ok, err := self.validator.isOwner(name)
|
addr, err := self.getAddressFromDataSig([]byte(name), signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Corrupt signature")
|
||||||
|
}
|
||||||
|
ok, err := self.validator.isOwner(name, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
|
|
@ -463,9 +465,13 @@ func parseUpdate(blob []byte) (period uint32, version uint32, ensname []byte, da
|
||||||
// It is the caller's responsibility to make sure that this data is not stale.
|
// 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
|
// A resource update cannot span chunks, and thus has max length 4096
|
||||||
func (self *ResourceHandler) Update(name string, data []byte) (Key, error) {
|
func (self *ResourceHandler) Update(name string, data []byte, signature [signatureLength]byte) (Key, error) {
|
||||||
|
|
||||||
ok, err := self.validator.isOwner(name)
|
addr, err := self.getAddressFromDataSig(data, signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid data/signature: %v", err)
|
||||||
|
}
|
||||||
|
ok, err := self.validator.isOwner(name, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
|
|
@ -501,34 +507,36 @@ func (self *ResourceHandler) Update(name string, data []byte) (Key, error) {
|
||||||
}
|
}
|
||||||
version++
|
version++
|
||||||
|
|
||||||
|
// create the update chunk
|
||||||
// prepend version and period to allow reverse lookups
|
// prepend version and period to allow reverse lookups
|
||||||
// data header length does NOT include the header length prefix bytes themselves
|
// data header length does NOT include the header length prefix bytes themselves
|
||||||
headerlength := uint16(len(resource.nameHash) + 4 + 4)
|
headerlength := uint16(len(resource.nameHash) + 4 + 4)
|
||||||
fulldata := make([]byte, int(headerlength)+2+len(data))
|
|
||||||
|
|
||||||
cursor := 0
|
|
||||||
binary.LittleEndian.PutUint16(fulldata, headerlength)
|
|
||||||
cursor += 2
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(fulldata[cursor:], nextperiod)
|
|
||||||
cursor += 4
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(fulldata[cursor:], version)
|
|
||||||
cursor += 4
|
|
||||||
|
|
||||||
copy(fulldata[cursor:], resource.nameHash[:])
|
|
||||||
cursor += len(resource.nameHash)
|
|
||||||
|
|
||||||
copy(fulldata[cursor:], data)
|
|
||||||
|
|
||||||
// create the update chunk and send it
|
|
||||||
key := self.resourceHash(resource.nameHash, nextperiod, version)
|
key := self.resourceHash(resource.nameHash, nextperiod, version)
|
||||||
chunk := NewChunk(key, nil)
|
chunk := NewChunk(key, nil)
|
||||||
chunk.SData, err = self.signContent(fulldata)
|
chunk.SData = make([]byte, signatureLength+int(headerlength)+2+len(data))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
cursor := 0
|
||||||
}
|
copy(chunk.SData, signature[:])
|
||||||
chunk.Size = int64(len(fulldata))
|
cursor += signatureLength
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength)
|
||||||
|
cursor += 2
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(chunk.SData[cursor:], nextperiod)
|
||||||
|
cursor += 4
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(chunk.SData[cursor:], version)
|
||||||
|
cursor += 4
|
||||||
|
|
||||||
|
copy(chunk.SData[cursor:], resource.nameHash[:])
|
||||||
|
cursor += len(resource.nameHash)
|
||||||
|
|
||||||
|
copy(chunk.SData[cursor:], data)
|
||||||
|
|
||||||
|
chunk.Size = int64(len(chunk.SData))
|
||||||
|
|
||||||
|
// send the chunk
|
||||||
self.Put(chunk)
|
self.Put(chunk)
|
||||||
log.Trace("resource update", "name", resource.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData)
|
log.Trace("resource update", "name", resource.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData)
|
||||||
|
|
||||||
|
|
@ -594,33 +602,33 @@ func (self *ResourceHandler) resourceHash(namehash common.Hash, period uint32, v
|
||||||
return self.hasher.Sum(nil)
|
return self.hasher.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ResourceHandler) signContent(data []byte) ([]byte, error) {
|
func (self *ResourceHandler) getContentAccount(chunkdata []byte) (common.Address, error) {
|
||||||
|
if len(chunkdata) <= signatureLength {
|
||||||
|
return common.Address{}, fmt.Errorf("zero-length data")
|
||||||
|
}
|
||||||
|
var signaturetype [signatureLength]byte
|
||||||
|
copy(signaturetype[:], chunkdata[:signatureLength])
|
||||||
|
return self.getAddressFromDataSig(chunkdata[signatureLength:], signaturetype)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ResourceHandler) getContentName(chunkdata []byte) (string, error) {
|
||||||
|
nameoffset := signatureLength + 2 + 4 + 4
|
||||||
|
if len(chunkdata) < nameoffset {
|
||||||
|
return "", fmt.Errorf("invalid chunk data")
|
||||||
|
}
|
||||||
|
namelength := binary.LittleEndian.Uint16(chunkdata[signatureLength : signatureLength+2])
|
||||||
|
namebytes := make([]byte, namelength)
|
||||||
|
copy(namebytes, chunkdata[nameoffset:nameoffset+int(namelength)-2-4-4])
|
||||||
|
return string(namebytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ResourceHandler) getAddressFromDataSig(data []byte, signature [signatureLength]byte) (common.Address, error) {
|
||||||
self.hashLock.Lock()
|
self.hashLock.Lock()
|
||||||
self.hasher.Reset()
|
self.hasher.Reset()
|
||||||
self.hasher.Write(data)
|
self.hasher.Write(data)
|
||||||
datahash := self.hasher.Sum(nil)
|
datahash := self.hasher.Sum(nil)
|
||||||
self.hashLock.Unlock()
|
self.hashLock.Unlock()
|
||||||
|
pub, err := crypto.SigToPub(datahash, signature[:])
|
||||||
signature, err := crypto.Sign(datahash, self.privKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
datawithsign := make([]byte, len(data)+signatureLength)
|
|
||||||
copy(datawithsign[:signatureLength], signature)
|
|
||||||
copy(datawithsign[signatureLength:], data)
|
|
||||||
return datawithsign, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ResourceHandler) getContentAccount(chunkdata []byte) (common.Address, error) {
|
|
||||||
if len(chunkdata) <= signatureLength {
|
|
||||||
return common.Address{}, fmt.Errorf("zero-length data")
|
|
||||||
}
|
|
||||||
self.hashLock.Lock()
|
|
||||||
self.hasher.Reset()
|
|
||||||
self.hasher.Write(chunkdata[signatureLength:])
|
|
||||||
datahash := self.hasher.Sum(nil)
|
|
||||||
self.hashLock.Unlock()
|
|
||||||
pub, err := crypto.SigToPub(datahash, chunkdata[:signatureLength])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -632,7 +640,16 @@ func (self *ResourceHandler) verifyContent(chunkdata []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Warn("ens owner lookup not implemented, verify will return true in all cases", "address", address)
|
name, err := self.getContentName(chunkdata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ok, err := self.validator.isOwner(name, address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ok {
|
||||||
|
return fmt.Errorf("not owner")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,25 @@ import (
|
||||||
|
|
||||||
// ENS validation of mutable resource owners
|
// ENS validation of mutable resource owners
|
||||||
type ENSValidator struct {
|
type ENSValidator struct {
|
||||||
owner common.Address
|
api *ens.ENS
|
||||||
api *ens.ENS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewENSValidator(owneraddress common.Address, contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts) (*ENSValidator, error) {
|
func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts) (*ENSValidator, error) {
|
||||||
var err error
|
var err error
|
||||||
validator := &ENSValidator{}
|
validator := &ENSValidator{}
|
||||||
validator.api, err = ens.NewENS(transactOpts, contractaddress, backend)
|
validator.api, err = ens.NewENS(transactOpts, contractaddress, backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
validator.owner = owneraddress
|
|
||||||
return validator, nil
|
return validator, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ENSValidator) isOwner(name string) (bool, error) {
|
func (self *ENSValidator) isOwner(name string, address common.Address) (bool, error) {
|
||||||
owneraddr, err := self.api.Owner(self.nameHash(name))
|
owneraddr, err := self.api.Owner(self.nameHash(name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return owneraddr == self.owner, nil
|
return owneraddr == address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ENSValidator) nameHash(name string) common.Hash {
|
func (self *ENSValidator) nameHash(name string) common.Hash {
|
||||||
|
|
@ -45,7 +43,7 @@ func NewGenericValidator(hashFunc func(string) common.Hash) *GenericValidator {
|
||||||
hashFunc: hashFunc,
|
hashFunc: hashFunc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (self *GenericValidator) isOwner(name string) (bool, error) {
|
func (self *GenericValidator) isOwner(name string, address common.Address) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,12 @@ func TestResourceReverseLookup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new resource
|
// create a new resource
|
||||||
rsrc, err := rh.NewResource(domainName, resourceFrequency)
|
signature, err := signContent(privkey, []byte(domainName))
|
||||||
|
if err != nil {
|
||||||
|
teardownTest(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rsrc, err := rh.NewResource(domainName, resourceFrequency, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +154,12 @@ func TestResourceReverseLookup(t *testing.T) {
|
||||||
// update data
|
// update data
|
||||||
fwdBlocks(int(resourceFrequency+1), backend)
|
fwdBlocks(int(resourceFrequency+1), backend)
|
||||||
data := []byte("foo")
|
data := []byte("foo")
|
||||||
resourcekey, err := rh.Update(domainName, data)
|
signature, err = signContent(privkey, data)
|
||||||
|
if err != nil {
|
||||||
|
teardownTest(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resourcekey, err := rh.Update(domainName, data, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +215,8 @@ func TestResourceHandler(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
_, err = rh.NewResource(domainName, resourceFrequency)
|
signature, err := signContent(privkey, []byte(resourcevalidname))
|
||||||
|
_, err = rh.NewResource(domainName, resourceFrequency, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -230,28 +241,48 @@ func TestResourceHandler(t *testing.T) {
|
||||||
// update halfway to first period
|
// update halfway to first period
|
||||||
resourcekey := make(map[string]Key)
|
resourcekey := make(map[string]Key)
|
||||||
fwdBlocks(int(resourceFrequency/2), backend)
|
fwdBlocks(int(resourceFrequency/2), backend)
|
||||||
resourcekey["blinky"], err = rh.Update(domainName, []byte("blinky"))
|
data := []byte("blinky")
|
||||||
|
signature, err = signContent(privkey, data)
|
||||||
|
if err != nil {
|
||||||
|
teardownTest(t, err)
|
||||||
|
}
|
||||||
|
resourcekey["blinky"], err = rh.Update(domainName, data, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update on first period
|
// update on first period
|
||||||
fwdBlocks(int(resourceFrequency/2), backend)
|
fwdBlocks(int(resourceFrequency/2), backend)
|
||||||
resourcekey["pinky"], err = rh.Update(domainName, []byte("pinky"))
|
data = []byte("pinky")
|
||||||
|
signature, err = signContent(privkey, data)
|
||||||
|
if err != nil {
|
||||||
|
teardownTest(t, err)
|
||||||
|
}
|
||||||
|
resourcekey["pinky"], err = rh.Update(domainName, data, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update on second period
|
// update on second period
|
||||||
fwdBlocks(int(resourceFrequency), backend)
|
fwdBlocks(int(resourceFrequency), backend)
|
||||||
resourcekey["inky"], err = rh.Update(domainName, []byte("inky"))
|
data = []byte("inky")
|
||||||
|
signature, err = signContent(privkey, data)
|
||||||
|
if err != nil {
|
||||||
|
teardownTest(t, err)
|
||||||
|
}
|
||||||
|
resourcekey["inky"], err = rh.Update(domainName, data, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update just after second period
|
// update just after second period
|
||||||
fwdBlocks(1, backend)
|
fwdBlocks(1, backend)
|
||||||
resourcekey["clyde"], err = rh.Update(domainName, []byte("clyde"))
|
data = []byte("clyde")
|
||||||
|
signature, err = signContent(privkey, data)
|
||||||
|
if err != nil {
|
||||||
|
teardownTest(t, err)
|
||||||
|
}
|
||||||
|
resourcekey["clyde"], err = rh.Update(domainName, data, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +381,7 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
validator, err := NewENSValidator(addr, contractAddr, contractbackend, transactOpts)
|
validator, err := NewENSValidator(contractAddr, contractbackend, transactOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -361,28 +392,29 @@ func TestResourceENSOwner(t *testing.T) {
|
||||||
teardownTest(t, err)
|
teardownTest(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signature, err := signContent(privkey, []byte(domainName))
|
||||||
|
if err != nil {
|
||||||
|
teardownTest(t, err)
|
||||||
|
}
|
||||||
// create new resource when we are owner = ok
|
// create new resource when we are owner = ok
|
||||||
_, err = rh.NewResource(domainName, 42)
|
_, err = rh.NewResource(domainName, 42, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, fmt.Errorf("Create resource fail: %v", err))
|
teardownTest(t, fmt.Errorf("Create resource fail: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data := []byte("foo")
|
||||||
|
signature, err = signContent(privkey, data)
|
||||||
|
|
||||||
// update resource when we are owner = ok
|
// update resource when we are owner = ok
|
||||||
_, err = rh.Update(domainName, []byte("foo"))
|
_, err = rh.Update(domainName, data, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
teardownTest(t, fmt.Errorf("Update resource fail: %v", err))
|
teardownTest(t, fmt.Errorf("Update resource fail: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new resource when we are NOT owner = !ok
|
// create new resource when we are NOT owner = !ok
|
||||||
addrtwo := crypto.PubkeyToAddress(privkeytwo.PublicKey)
|
signaturetwo, err := signContent(privkeytwo, data)
|
||||||
validator.owner = addrtwo
|
|
||||||
|
|
||||||
_, err = rh.NewResource(domainName, 42)
|
|
||||||
if err == nil {
|
|
||||||
teardownTest(t, fmt.Errorf("Expected resource create fail due to owner mismatch"))
|
|
||||||
}
|
|
||||||
// update resource when we are owner = ok
|
// update resource when we are owner = ok
|
||||||
_, err = rh.Update(domainName, []byte("foo"))
|
_, err = rh.Update(domainName, data, signaturetwo)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
teardownTest(t, fmt.Errorf("Expected resource update fail due to owner mismatch"))
|
teardownTest(t, fmt.Errorf("Expected resource update fail due to owner mismatch"))
|
||||||
}
|
}
|
||||||
|
|
@ -503,6 +535,20 @@ func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string,
|
||||||
return contractAddress, contractBackend, nil
|
return contractAddress, contractBackend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func signContent(privKey *ecdsa.PrivateKey, data []byte) ([signatureLength]byte, error) {
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(data)
|
||||||
|
datahash := hasher.Sum(nil)
|
||||||
|
|
||||||
|
signature, err := crypto.Sign(datahash, privKey)
|
||||||
|
if err != nil {
|
||||||
|
return [signatureLength]byte{}, err
|
||||||
|
}
|
||||||
|
var signaturetype [signatureLength]byte
|
||||||
|
copy(signaturetype[:], signature)
|
||||||
|
return signaturetype, nil
|
||||||
|
}
|
||||||
|
|
||||||
type testCloudStore struct {
|
type testCloudStore struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue