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.
|
||||
|
||||
type ResourceValidator interface {
|
||||
isOwner(string) (bool, error)
|
||||
isOwner(string, common.Address) (bool, error)
|
||||
nameHash(string) common.Hash
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +105,6 @@ type ResourceHandler struct {
|
|||
hashLock sync.Mutex
|
||||
resourceLock sync.RWMutex
|
||||
hasher SwarmHash
|
||||
privKey *ecdsa.PrivateKey
|
||||
maxChunkData int64
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +126,6 @@ func NewResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore Cl
|
|||
rpcClient: rpcClient,
|
||||
resources: make(map[string]*resource),
|
||||
hasher: hasher(),
|
||||
privKey: privKey,
|
||||
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`.
|
||||
//
|
||||
// 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 {
|
||||
return nil, err
|
||||
} 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.
|
||||
//
|
||||
// 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 {
|
||||
return nil, err
|
||||
} else if !ok {
|
||||
|
|
@ -501,34 +507,36 @@ func (self *ResourceHandler) Update(name string, data []byte) (Key, error) {
|
|||
}
|
||||
version++
|
||||
|
||||
// create the update chunk
|
||||
// prepend version and period to allow reverse lookups
|
||||
// data header length does NOT include the header length prefix bytes themselves
|
||||
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)
|
||||
chunk := NewChunk(key, nil)
|
||||
chunk.SData, err = self.signContent(fulldata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chunk.Size = int64(len(fulldata))
|
||||
chunk.SData = make([]byte, signatureLength+int(headerlength)+2+len(data))
|
||||
|
||||
cursor := 0
|
||||
copy(chunk.SData, signature[:])
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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.hasher.Reset()
|
||||
self.hasher.Write(data)
|
||||
datahash := self.hasher.Sum(nil)
|
||||
self.hashLock.Unlock()
|
||||
|
||||
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])
|
||||
pub, err := crypto.SigToPub(datahash, signature[:])
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
|
@ -632,7 +640,16 @@ func (self *ResourceHandler) verifyContent(chunkdata []byte) error {
|
|||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,27 +8,25 @@ import (
|
|||
|
||||
// ENS validation of mutable resource owners
|
||||
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
|
||||
validator := &ENSValidator{}
|
||||
validator.api, err = ens.NewENS(transactOpts, contractaddress, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validator.owner = owneraddress
|
||||
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))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return owneraddr == self.owner, nil
|
||||
return owneraddr == address, nil
|
||||
}
|
||||
|
||||
func (self *ENSValidator) nameHash(name string) common.Hash {
|
||||
|
|
@ -45,7 +43,7 @@ func NewGenericValidator(hashFunc func(string) common.Hash) *GenericValidator {
|
|||
hashFunc: hashFunc,
|
||||
}
|
||||
}
|
||||
func (self *GenericValidator) isOwner(name string) (bool, error) {
|
||||
func (self *GenericValidator) isOwner(name string, address common.Address) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,12 @@ func TestResourceReverseLookup(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
|
|
@ -149,7 +154,12 @@ func TestResourceReverseLookup(t *testing.T) {
|
|||
// update data
|
||||
fwdBlocks(int(resourceFrequency+1), backend)
|
||||
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 {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
|
|
@ -205,7 +215,8 @@ func TestResourceHandler(t *testing.T) {
|
|||
if err != nil {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
_, err = rh.NewResource(domainName, resourceFrequency)
|
||||
signature, err := signContent(privkey, []byte(resourcevalidname))
|
||||
_, err = rh.NewResource(domainName, resourceFrequency, signature)
|
||||
if err != nil {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
|
|
@ -230,28 +241,48 @@ func TestResourceHandler(t *testing.T) {
|
|||
// update halfway to first period
|
||||
resourcekey := make(map[string]Key)
|
||||
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 {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
|
||||
// update on first period
|
||||
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 {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
|
||||
// update on second period
|
||||
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 {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
|
||||
// update just after second period
|
||||
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 {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
|
|
@ -350,7 +381,7 @@ func TestResourceENSOwner(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validator, err := NewENSValidator(addr, contractAddr, contractbackend, transactOpts)
|
||||
validator, err := NewENSValidator(contractAddr, contractbackend, transactOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -361,28 +392,29 @@ func TestResourceENSOwner(t *testing.T) {
|
|||
teardownTest(t, err)
|
||||
}
|
||||
|
||||
signature, err := signContent(privkey, []byte(domainName))
|
||||
if err != nil {
|
||||
teardownTest(t, err)
|
||||
}
|
||||
// create new resource when we are owner = ok
|
||||
_, err = rh.NewResource(domainName, 42)
|
||||
_, err = rh.NewResource(domainName, 42, signature)
|
||||
if err != nil {
|
||||
teardownTest(t, fmt.Errorf("Create resource fail: %v", err))
|
||||
}
|
||||
|
||||
data := []byte("foo")
|
||||
signature, err = signContent(privkey, data)
|
||||
|
||||
// update resource when we are owner = ok
|
||||
_, err = rh.Update(domainName, []byte("foo"))
|
||||
_, err = rh.Update(domainName, data, signature)
|
||||
if err != nil {
|
||||
teardownTest(t, fmt.Errorf("Update resource fail: %v", err))
|
||||
}
|
||||
|
||||
// create new resource when we are NOT owner = !ok
|
||||
addrtwo := crypto.PubkeyToAddress(privkeytwo.PublicKey)
|
||||
validator.owner = addrtwo
|
||||
|
||||
_, err = rh.NewResource(domainName, 42)
|
||||
if err == nil {
|
||||
teardownTest(t, fmt.Errorf("Expected resource create fail due to owner mismatch"))
|
||||
}
|
||||
signaturetwo, err := signContent(privkeytwo, data)
|
||||
// update resource when we are owner = ok
|
||||
_, err = rh.Update(domainName, []byte("foo"))
|
||||
_, err = rh.Update(domainName, data, signaturetwo)
|
||||
if err == nil {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue