diff --git a/swarm/storage/resource.go b/swarm/storage/resource.go index 51010d7ca4..342727ca5b 100644 --- a/swarm/storage/resource.go +++ b/swarm/storage/resource.go @@ -25,14 +25,14 @@ const ( type Signature [signatureLength]byte -var emptySignature Signature - type SignFunc func(common.Hash) (Signature, error) -// Encapsulates an actual resource update. When synced it contains the most recent +type nameHashFunc func(string) common.Hash + +// Encapsulates an specific resource update. When synced it contains the most recent // version of the resource update data. type resource struct { - name string + name *string nameHash common.Hash startBlock uint64 lastPeriod uint32 @@ -47,6 +47,14 @@ func (r *resource) isSynced() bool { return !r.updated.IsZero() } +// Implement to activate validation of resource updates +// Specifically signing data and verification of signatures +type ResourceValidator interface { + checkAccess(string, common.Address) (bool, error) + nameHash(string) common.Hash // nameHashFunc + sign(common.Hash) (Signature, error) // SignFunc +} + // Mutable resource is an entity which allows updates to a resource // without resorting to ENS on each update. // The update scheme is built on swarm chunks with chunk keys following @@ -56,8 +64,9 @@ func (r *resource) isSynced() bool { // expressed in terms of number of blocks. // // The root entry of a mutable resource is tied to a unique identifier, -// typically - but not necessarily - an ens name. It also contains the -// block number when the resource update was first registered, and +// typically - but not necessarily - an ens name. The identifier must be +// an valid IDNA string. It also contains the block number +// when the resource update was first registered, and // the block frequency with which the resource will be updated, both of // which are stored as little-endian uint64 values in the database (for a // total of 16 bytes). @@ -68,10 +77,6 @@ func (r *resource) isSynced() bool { // starting at block 4200 with frequency 42 will have updates on block 4242, // 4284, 4326 and so on. // -// The identifier is supplied as a string, but will be IDNA converted and -// passed through the ENS namehash function. Pure ascii identifiers without -// periods will thus merely be hashed. -// // Note that the root entry is not required for the resource update scheme to // work. A normal chunk of the blocknumber/frequency data can also be created, // and pointed to by an external resource (ENS or manifest entry) @@ -79,7 +84,7 @@ func (r *resource) isSynced() bool { // Actual data updates are also made in the form of swarm chunks. The keys // of the updates are the hash of a concatenation of properties as follows: // -// sha256(namehash|period|version) +// sha256(period|version|namehash) // // The period is (currentblock - startblock) / frequency // @@ -91,8 +96,12 @@ func (r *resource) isSynced() bool { // // A lookup agent need only know the identifier name in order to get the versions // -// the chunk data is: sign(resourcedata)|resourcedata -// the resourcedata is: headerlength|period|version|name|data +// the resourcedata is: +// headerlength|period|version|identifier|data +// +// if a validator is active, the chunk data is: +// sign(resourcedata)|resourcedata +// otherwise, the chunk data is the same as the resourcedata // // headerlength is a 16 bit value containing the byte length of period|version|name // period and version are both 32 bit values. name can have arbitrary length @@ -103,13 +112,6 @@ func (r *resource) isSynced() bool { // stored using a separate store, and forwarding/syncing protocols carry per-chunk // flags to tell whether the chunk can be validated or not; if not it is to be // treated as a resource update chunk. - -type ResourceValidator interface { - checkAccess(string, common.Address) (bool, error) - nameHash(string) common.Hash - sign(common.Hash) (Signature, error) // SignFunc -} - type ResourceHandler struct { ChunkStore validator ResourceValidator @@ -118,9 +120,12 @@ type ResourceHandler struct { hashLock sync.Mutex resourceLock sync.RWMutex hasher SwarmHash + nameHash nameHashFunc } // Create or open resource update chunk store +// +// If validator is nil, signature and access validation will be deactivated func NewResourceHandler(datadir string, cloudStore CloudStore, rpcClient *rpc.Client, validator ResourceValidator) (*ResourceHandler, error) { hashfunc := MakeHashFunc(SHA3Hash) @@ -140,18 +145,19 @@ func NewResourceHandler(datadir string, cloudStore CloudStore, rpcClient *rpc.Cl rpcClient: rpcClient, resources: make(map[string]*resource), hasher: hashfunc(), + validator: validator, } - if validator != nil { - rh.validator = validator + if rh.validator != nil { + rh.nameHash = rh.validator.nameHash } else { - rh.validator = NewGenericValidator(func(name string) common.Hash { + rh.nameHash = func(name string) common.Hash { rh.hashLock.Lock() defer rh.hashLock.Unlock() rh.hasher.Reset() rh.hasher.Write([]byte(name)) return common.BytesToHash(rh.hasher.Sum(nil)) - }, nil) + } } return rh, nil @@ -167,26 +173,20 @@ func (self *ResourceHandler) chunkSize() int64 { // The signature data should match the hash of the idna-converted name by the validator's namehash function, NOT the raw name bytes. // // 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, verify bool) (*resource, error) { +func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resource, error) { // frequency 0 is invalid if frequency == 0 { return nil, fmt.Errorf("Frequency cannot be 0") } - // must have name - if name == "" { - return nil, fmt.Errorf("Empty name") + if !isSafeName(name) { + return nil, fmt.Errorf("Invalid name: '%s'", name) } - validName, err := toSafeName(name) - if err != nil { - return nil, err - } + nameHash := self.nameHash(name) - nameHash := self.validator.nameHash(validName) - - if verify { + if self.validator != nil { signature, err := self.validator.sign(nameHash) if err != nil { return nil, fmt.Errorf("Sign fail: %v", err) @@ -220,10 +220,10 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64, verify b binary.LittleEndian.PutUint64(val, frequency) copy(chunk.SData[8:], val) self.Put(chunk) - log.Debug("new resource", "name", validName, "key", nameHash, "startBlock", currentblock, "frequency", frequency) + log.Debug("new resource", "name", name, "key", nameHash, "startBlock", currentblock, "frequency", frequency) rsrc := &resource{ - name: validName, + name: &name, nameHash: nameHash, startBlock: currentblock, frequency: frequency, @@ -231,7 +231,7 @@ func (self *ResourceHandler) NewResource(name string, frequency uint64, verify b } self.setResource(name, rsrc) - return self.resources[name], nil + return rsrc, nil } // Searches and retrieves the specific version of the resource update identified by `name` @@ -247,7 +247,7 @@ func (self *ResourceHandler) LookupVersion(name string, period uint32, version u if err != nil { return nil, err } - return self.lookup(rsrc, name, period, version, refresh) + return self.lookup(rsrc, period, version, refresh) } // Retrieves the latest version of the resource update identified by `name` @@ -263,7 +263,7 @@ func (self *ResourceHandler) LookupHistorical(name string, period uint32, refres if err != nil { return nil, err } - return self.lookup(rsrc, name, period, 0, refresh) + return self.lookup(rsrc, period, 0, refresh) } // Retrieves the latest version of the resource update identified by `name` @@ -288,11 +288,11 @@ func (self *ResourceHandler) LookupLatest(name string, refresh bool) (*resource, return nil, err } nextperiod := getNextPeriod(rsrc.startBlock, currentblock, rsrc.frequency) - return self.lookup(rsrc, name, nextperiod, 0, refresh) + return self.lookup(rsrc, nextperiod, 0, refresh) } // base code for public lookup methods -func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32, version uint32, refresh bool) (*resource, error) { +func (self *ResourceHandler) lookup(rsrc *resource, period uint32, version uint32, refresh bool) (*resource, error) { if period == 0 { return nil, fmt.Errorf("period must be >0") @@ -312,7 +312,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32, chunk, err := self.Get(key) if err == nil { if specificversion { - return self.updateResourceIndex(rsrc, chunk, &name) + return self.updateResourceIndex(rsrc, chunk) } // check if we have versions > 1. If a version fails, the previous version is used and returned. log.Trace("rsrc update version 1 found, checking for version updates", "period", period, "key", key) @@ -321,7 +321,7 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32, key := self.resourceHash(period, newversion, rsrc.nameHash) newchunk, err := self.Get(key) if err != nil { - return self.updateResourceIndex(rsrc, chunk, &name) + return self.updateResourceIndex(rsrc, chunk) } log.Trace("version update found, checking next", "version", version, "period", period, "key", key) chunk = newchunk @@ -336,19 +336,18 @@ func (self *ResourceHandler) lookup(rsrc *resource, name string, period uint32, // load existing mutable resource into resource struct func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource, error) { + // if the resource is not known to this session we must load it // if refresh is set, we force load - rsrc := self.getResource(name) if rsrc == nil || refresh { rsrc = &resource{} // make sure our name is safe to use - validname, err := toSafeName(name) - if err != nil { - return nil, err + if !isSafeName(name) { + return nil, fmt.Errorf("Invalid name '%s'") } - rsrc.name = validname - rsrc.nameHash = self.validator.nameHash(validname) + rsrc.name = &name + rsrc.nameHash = self.nameHash(name) // get the root info chunk and update the cached value chunk, err := self.Get(Key(rsrc.nameHash[:])) @@ -356,7 +355,7 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource, return nil, err } - // sanity check for chunk data + // minimum sanity check for chunk data if len(chunk.SData) != indexSize { return nil, fmt.Errorf("Invalid chunk length %d, should be %d", len(chunk.SData), indexSize) } @@ -372,46 +371,58 @@ func (self *ResourceHandler) loadResource(name string, refresh bool) (*resource, } // update mutable resource index map with specified content -func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk, indexname *string) (*resource, error) { +func (self *ResourceHandler) updateResourceIndex(rsrc *resource, chunk *Chunk) (*resource, error) { - // update our rsrcs entry map - signature, period, version, name, data, err := parseUpdate(chunk.SData) - if rsrc.name != name { + // retrieve metadata from chunk data and check that it matches this mutable resource + signature, period, version, name, data, err := self.parseUpdate(chunk.SData) + if *rsrc.name != name { return nil, fmt.Errorf("Update belongs to '%s', but have '%s'", name, rsrc.name) } - self.hashLock.Lock() - self.hasher.Reset() - self.hasher.Write(chunk.Key[:]) - self.hasher.Write(data) - digest := self.hasher.Sum(nil) - self.hashLock.Unlock() - _, err = getAddressFromDataSig(common.BytesToHash(digest), signature) - if err != nil { - return nil, fmt.Errorf("Invalid signature: %v", err) + // only check signature if validator is present + if self.validator != nil { + digest := self.keyDataHash(chunk.Key, data) + _, err = getAddressFromDataSig(digest, *signature) + if err != nil { + return nil, fmt.Errorf("Invalid signature: %v", err) + } } + + // update our rsrcs entry map rsrc.lastPeriod = period rsrc.version = version rsrc.updated = time.Now() rsrc.data = make([]byte, len(data)) copy(rsrc.data, data) - log.Debug("Resource synced", "name", rsrc.name, "key", chunk.Key, "period", rsrc.lastPeriod, "version", rsrc.version) - self.setResource(*indexname, rsrc) + log.Debug("Resource synced", "name", *rsrc.name, "key", chunk.Key, "period", rsrc.lastPeriod, "version", rsrc.version) + self.setResource(*rsrc.name, rsrc) return rsrc, nil } -func parseUpdate(chunkdata []byte) (signature Signature, period uint32, version uint32, name string, data []byte, err error) { - copy(signature[:], chunkdata[:signatureLength]) - cursor := signatureLength +// retrieve update metadata from chunk data +// mirrors newUpdateChunk() +func (self *ResourceHandler) parseUpdate(chunkdata []byte) (signature *Signature, period uint32, version uint32, name string, data []byte, err error) { + cursor := 0 + + // omit signatures if we have no validator + var sigoffset int + if self.validator != nil { + signature = &Signature{} + copy(signature[:], chunkdata[:signatureLength]) + sigoffset = signatureLength + cursor = sigoffset + } + headerlength := binary.LittleEndian.Uint16(chunkdata[cursor : cursor+2]) if int(headerlength+2) > len(chunkdata) { - return emptySignature, 0, 0, "", nil, fmt.Errorf("Reported header length %d longer than actual data length %d", headerlength, len(chunkdata)) + err = fmt.Errorf("Reported header length %d longer than actual data length %d", headerlength, len(chunkdata)) + return } cursor += 2 period = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4]) cursor += 4 version = binary.LittleEndian.Uint32(chunkdata[cursor : cursor+4]) cursor += 4 - namelength := int(headerlength) - cursor + signatureLength + 2 + namelength := int(headerlength) - cursor + sigoffset + 2 name = string(chunkdata[cursor : cursor+namelength]) cursor += namelength data = make([]byte, len(chunkdata)-cursor) @@ -425,16 +436,24 @@ func parseUpdate(chunkdata []byte) (signature Signature, period uint32, version // 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(indexname string, data []byte) (Key, error) { +func (self *ResourceHandler) Update(name string, data []byte) (Key, error) { + + var sigoffset int + if self.validator != nil { + sigoffset = signatureLength + } // get the cached information - rsrc := self.getResource(indexname) + rsrc := self.getResource(name) + if rsrc == nil { + return nil, fmt.Errorf("Resource object not in index") + } if !rsrc.isSynced() { return nil, fmt.Errorf("Resource object not in sync") } // an update can be only one chunk long - datalimit := self.chunkSize() - int64(signatureLength-len(self.resources[indexname].name)-8) + datalimit := self.chunkSize() - int64(sigoffset-len(name)-8) if int64(len(data)) > datalimit { return nil, fmt.Errorf("Data overflow: %d / %d bytes", len(data), datalimit) } @@ -449,7 +468,7 @@ func (self *ResourceHandler) Update(indexname string, data []byte) (Key, error) // if we already have an update for this block then increment version // (resource object MUST be in sync for version to be correct) var version uint32 - if self.hasUpdate(indexname, nextperiod) { + if self.hasUpdate(name, nextperiod) { version = rsrc.version } version++ @@ -457,32 +476,36 @@ func (self *ResourceHandler) Update(indexname string, data []byte) (Key, error) // calculate the chunk key key := self.resourceHash(nextperiod, version, rsrc.nameHash) - // sign the data hash with the key - digest := self.keyDataHash(key, data) - signature, err := self.validator.sign(digest) - if err != nil { - return nil, err + var signature *Signature + if self.validator != nil { + // sign the data hash with the key + digest := self.keyDataHash(key, data) + sig, err := self.validator.sign(digest) + if err != nil { + return nil, err + } + signature = &sig + + // get the address of the signer (which also checks that it's a valid signature) + addr, err := getAddressFromDataSig(digest, *signature) + if err != nil { + return nil, fmt.Errorf("Invalid data/signature: %v", err) + } + + // check if the signer has access to update + ok, err := self.validator.checkAccess(name, addr) + if err != nil { + return nil, err + } else if !ok { + return nil, fmt.Errorf("Address %x does not have access to update %s", addr, name) + } } - // get the address of the signer (which also checks that it's a valid signature) - addr, err := getAddressFromDataSig(digest, signature) - if err != nil { - return nil, fmt.Errorf("Invalid data/signature: %v", err) - } - - // check if the signer has access to update - ok, err := self.validator.checkAccess(indexname, addr) - if err != nil { - return nil, err - } else if !ok { - return nil, fmt.Errorf("Address %x does not have access to update %s", addr, indexname) - } - - chunk := newUpdateChunk(key, signature, nextperiod, version, self.resources[indexname].name, data) + chunk := newUpdateChunk(key, signature, nextperiod, version, name, data) // send the chunk self.Put(chunk) - log.Trace("resource update", "name", rsrc.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData) + log.Trace("resource update", "name", name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData) // update our resources map entry and return the new key rsrc.lastPeriod = nextperiod @@ -492,38 +515,6 @@ func (self *ResourceHandler) Update(indexname string, data []byte) (Key, error) return key, nil } -func newUpdateChunk(key Key, signature Signature, period uint32, version uint32, name string, data []byte) *Chunk { - // create the update chunk - // prepend version and period to allow reverse lookups - headerlength := uint16(len(name) + 4 + 4) - - chunk := NewChunk(key, nil) - chunk.SData = make([]byte, signatureLength+int(headerlength)+2+len(data)) - - cursor := 0 - copy(chunk.SData, signature[:]) - cursor += signatureLength - - // data header length does NOT include the header length prefix bytes themselves - binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength) - cursor += 2 - - binary.LittleEndian.PutUint32(chunk.SData[cursor:], period) - cursor += 4 - - binary.LittleEndian.PutUint32(chunk.SData[cursor:], version) - cursor += 4 - - namebytes := []byte(name) - copy(chunk.SData[cursor:], namebytes) - cursor += len(namebytes) - - copy(chunk.SData[cursor:], data) - - chunk.Size = int64(len(chunk.SData)) - return chunk -} - // Closes the datastore. // Always call this at shutdown to avoid data corruption. func (self *ResourceHandler) Close() { @@ -543,10 +534,12 @@ func (self *ResourceHandler) getBlock() (uint64, error) { return strconv.ParseUint(currentblock, 10, 64) } +// Calculate the period index (aka major version number) from a given block number func (self *ResourceHandler) BlockToPeriod(name string, blocknumber uint64) uint32 { return getNextPeriod(self.resources[name].startBlock, blocknumber, self.resources[name].frequency) } +// Calculate the block number from a given period index (aka major version number) func (self *ResourceHandler) PeriodToBlock(name string, period uint32) uint64 { return self.resources[name].startBlock + (uint64(period) * self.resources[name].frequency) } @@ -564,8 +557,9 @@ func (self *ResourceHandler) setResource(name string, rsrc *resource) { self.resources[name] = rsrc } +// used for chunk keys func (self *ResourceHandler) resourceHash(period uint32, version uint32, namehash common.Hash) Key { - // format is: hash(namehash|period|version) + // format is: hash(period|version|namehash) self.hashLock.Lock() defer self.hashLock.Unlock() self.hasher.Reset() @@ -578,6 +572,13 @@ func (self *ResourceHandler) resourceHash(period uint32, version uint32, namehas return self.hasher.Sum(nil) } +func (self *ResourceHandler) hasUpdate(name string, period uint32) bool { + if self.resources[name].lastPeriod == period { + return true + } + return false +} + func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Address, error) { pub, err := crypto.SigToPub(datahash.Bytes(), signature[:]) if err != nil { @@ -586,8 +587,45 @@ func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Ad return crypto.PubkeyToAddress(*pub), nil } -func (self *ResourceHandler) hasUpdate(name string, period uint32) bool { - return self.resources[name].lastPeriod == period +// create an update chunk +func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32, name string, data []byte) *Chunk { + + // no signatures if no validator + var sigoffset int + if signature != nil { + sigoffset = signatureLength + } + + // prepend version and period to allow reverse lookups + headerlength := uint16(len(name) + 4 + 4) + + chunk := NewChunk(key, nil) + chunk.SData = make([]byte, sigoffset+int(headerlength)+2+len(data)) + + cursor := 0 + if signature != nil { + copy(chunk.SData, (*signature)[:]) + cursor += signatureLength + } + + // data header length does NOT include the header length prefix bytes themselves + binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength) + cursor += 2 + + binary.LittleEndian.PutUint32(chunk.SData[cursor:], period) + cursor += 4 + + binary.LittleEndian.PutUint32(chunk.SData[cursor:], version) + cursor += 4 + + namebytes := []byte(name) + copy(chunk.SData[cursor:], namebytes) + cursor += len(namebytes) + + copy(chunk.SData[cursor:], data) + + chunk.Size = int64(len(chunk.SData)) + return chunk } // \TODO chunkSize is a workaround until the ChunkStore interface exports a method to get the chunk size directly @@ -640,8 +678,7 @@ func getNextPeriod(start uint64, current uint64, frequency uint64) uint32 { return uint32(period + 1) } -func toSafeName(name string) (string, error) { - // make sure our ens identifier is idna safe +func ToSafeName(name string) (string, error) { validname, err := idna.ToASCII(name) if err != nil { return "", err @@ -649,6 +686,22 @@ func toSafeName(name string) (string, error) { return validname, nil } +// check that name identifiers contain valid bytes +func isSafeName(name string) bool { + if name == "" { + return false + } + validname, err := idna.ToASCII(name) + if err != nil { + return false + } + if validname != name { + return false + } + return true +} + +// convenience for creating signature hashes of update data func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash { self.hashLock.Lock() defer self.hashLock.Unlock() diff --git a/swarm/storage/resource_ens.go b/swarm/storage/resource_ens.go index ccd4ed5344..0a4500309d 100644 --- a/swarm/storage/resource_ens.go +++ b/swarm/storage/resource_ens.go @@ -12,9 +12,9 @@ type baseValidator struct { signFunc SignFunc } -func (b *baseValidator) sign(datahash common.Hash) (Signature, error) { +func (b *baseValidator) sign(datahash common.Hash) (signature Signature, err error) { if b.signFunc == nil { - return emptySignature, fmt.Errorf("No signature function") + return signature, fmt.Errorf("No signature function") } return b.signFunc(datahash) } @@ -22,8 +22,7 @@ func (b *baseValidator) sign(datahash common.Hash) (Signature, error) { // ENS validation of mutable resource owners type ENSValidator struct { *baseValidator - api *ens.ENS - hashlength int + api *ens.ENS } func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts, signFunc SignFunc) (*ENSValidator, error) { @@ -37,7 +36,6 @@ func NewENSValidator(contractaddress common.Address, backend bind.ContractBacken if err != nil { return nil, err } - validator.hashlength = len(ens.EnsNode(dbDirName).Bytes()) return validator, nil } @@ -52,29 +50,3 @@ func (self *ENSValidator) checkAccess(name string, address common.Address) (bool func (self *ENSValidator) nameHash(name string) common.Hash { return ens.EnsNode(name) } - -// Default fallthrough validation of mutable resource ownership -type GenericValidator struct { - *baseValidator - hashFunc func(string) common.Hash - hashlength int -} - -func NewGenericValidator(hashFunc func(string) common.Hash, signFunc SignFunc) *GenericValidator { - return &GenericValidator{ - baseValidator: &baseValidator{ - signFunc: signFunc, - }, - hashFunc: hashFunc, - hashlength: len(hashFunc(dbDirName).Bytes()), - } - -} - -func (self *GenericValidator) checkAccess(name string, address common.Address) (bool, error) { - return true, nil -} - -func (self *GenericValidator) nameHash(name string) common.Hash { - return self.hashFunc(name) -} diff --git a/swarm/storage/resource_test.go b/swarm/storage/resource_test.go index f645522054..ffa0eec758 100644 --- a/swarm/storage/resource_test.go +++ b/swarm/storage/resource_test.go @@ -15,8 +15,6 @@ import ( "testing" "time" - "golang.org/x/net/idna" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" @@ -35,10 +33,16 @@ var ( resourceFrequency = uint64(42) cleanF func() domainName = "føø.bar" + safeName string ) func init() { + var err error log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) + safeName, err = ToSafeName(domainName) + if err != nil { + panic(err) + } } // simulated backend does not have the blocknumber call @@ -70,20 +74,20 @@ func TestResourceReverse(t *testing.T) { period := uint32(4) version := uint32(2) - // set up rpc and create resourcehandler - rh, _, signer, teardownTest, err := setupTest(nil, nil) + // signer containing private key + signer, err := newTestSigner() if err != nil { - teardownTest(t, err) + t.Fatal(err) } - // create a new resource - validname, err := idna.ToASCII(domainName) + // set up rpc and create resourcehandler + rh, _, _, teardownTest, err := setupTest(nil, newTestValidator(signer.signContent)) if err != nil { teardownTest(t, err) } // generate a hash for block 4200 version 1 - key := rh.resourceHash(period, version, rh.validator.nameHash(validname)) + key := rh.resourceHash(period, version, rh.nameHash(safeName)) // generate some bogus data for the chunk and sign it data := make([]byte, 8) @@ -99,17 +103,18 @@ func TestResourceReverse(t *testing.T) { teardownTest(t, err) } - chunk := newUpdateChunk(key, sig, period, version, validname, data) + chunk := newUpdateChunk(key, &sig, period, version, safeName, data) // check that we can recover the owner account from the update chunk's signature - checksig, checkperiod, checkversion, checkname, checkdata, err := parseUpdate(chunk.SData) + checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData) checkdigest := rh.keyDataHash(chunk.Key, checkdata) - recoveredaddress, err := getAddressFromDataSig(checkdigest, checksig) + recoveredaddress, err := getAddressFromDataSig(checkdigest, *checksig) if err != nil { - teardownTest(t, err) + teardownTest(t, fmt.Errorf("Retrieve address from signature fail: %v", err)) } originaladdress := crypto.PubkeyToAddress(signer.privKey.PublicKey) + // check that the metadata retrieved from the chunk matches what we gave it if recoveredaddress != originaladdress { teardownTest(t, fmt.Errorf("addresses dont match: %x != %x", originaladdress, recoveredaddress)) } @@ -123,8 +128,8 @@ func TestResourceReverse(t *testing.T) { if version != checkversion { teardownTest(t, fmt.Errorf("Expected version '%d', was '%d'", version, checkversion)) } - if validname != checkname { - teardownTest(t, fmt.Errorf("Expected name '%s', was '%s'", validname, checkname)) + if safeName != checkname { + teardownTest(t, fmt.Errorf("Expected name '%s', was '%s'", safeName, checkname)) } if !bytes.Equal(data, checkdata) { teardownTest(t, fmt.Errorf("Expectedn data '%x', was '%x'", data, checkdata)) @@ -145,17 +150,16 @@ func TestResourceHandler(t *testing.T) { } // create a new resource - resourcevalidname, err := idna.ToASCII(domainName) if err != nil { teardownTest(t, err) } - _, err = rh.NewResource(domainName, resourceFrequency, false) + _, err = rh.NewResource(safeName, resourceFrequency) if err != nil { teardownTest(t, err) } // check that the new resource is stored correctly - namehash := rh.validator.nameHash(resourcevalidname) + namehash := rh.nameHash(safeName) chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(Key(namehash[:])) if err != nil { teardownTest(t, err) @@ -175,7 +179,7 @@ func TestResourceHandler(t *testing.T) { resourcekey := make(map[string]Key) fwdBlocks(int(resourceFrequency/2), backend) data := []byte("blinky") - resourcekey["blinky"], err = rh.Update(domainName, data) + resourcekey["blinky"], err = rh.Update(safeName, data) if err != nil { teardownTest(t, err) } @@ -183,7 +187,7 @@ func TestResourceHandler(t *testing.T) { // update on first period fwdBlocks(int(resourceFrequency/2), backend) data = []byte("pinky") - resourcekey["pinky"], err = rh.Update(domainName, data) + resourcekey["pinky"], err = rh.Update(safeName, data) if err != nil { teardownTest(t, err) } @@ -191,7 +195,7 @@ func TestResourceHandler(t *testing.T) { // update on second period fwdBlocks(int(resourceFrequency), backend) data = []byte("inky") - resourcekey["inky"], err = rh.Update(domainName, data) + resourcekey["inky"], err = rh.Update(safeName, data) if err != nil { teardownTest(t, err) } @@ -199,7 +203,7 @@ func TestResourceHandler(t *testing.T) { // update just after second period fwdBlocks(1, backend) data = []byte("clyde") - resourcekey["clyde"], err = rh.Update(domainName, data) + resourcekey["clyde"], err = rh.Update(safeName, data) if err != nil { teardownTest(t, err) } @@ -211,43 +215,44 @@ func TestResourceHandler(t *testing.T) { fwdBlocks(int(resourceFrequency*2)-1, backend) rh2, err := NewResourceHandler(datadir, &testCloudStore{}, rh.rpcClient, nil) - _, err = rh2.LookupLatest(domainName, true) + _, err = rh2.LookupLatest(safeName, true) if err != nil { teardownTest(t, err) } // last update should be "clyde", version two, blockheight startblocknumber + (resourcefrequency * 3) - if !bytes.Equal(rh2.resources[domainName].data, []byte("clyde")) { - teardownTest(t, fmt.Errorf("resource data was %v, expected %v", rh2.resources[domainName].data, []byte("clyde"))) + if !bytes.Equal(rh2.resources[safeName].data, []byte("clyde")) { + teardownTest(t, fmt.Errorf("resource data was %v, expected %v", rh2.resources[safeName].data, []byte("clyde"))) } - if rh2.resources[domainName].version != 2 { - teardownTest(t, fmt.Errorf("resource version was %d, expected 2", rh2.resources[domainName].version)) + if rh2.resources[safeName].version != 2 { + teardownTest(t, fmt.Errorf("resource version was %d, expected 2", rh2.resources[safeName].version)) } - if rh2.resources[domainName].lastPeriod != 3 { - teardownTest(t, fmt.Errorf("resource period was %d, expected 3", rh2.resources[domainName].lastPeriod)) + if rh2.resources[safeName].lastPeriod != 3 { + teardownTest(t, fmt.Errorf("resource period was %d, expected 3", rh2.resources[safeName].lastPeriod)) } + log.Debug("Latest lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data) // specific block, latest version - rsrc, err := rh2.LookupHistorical(domainName, 3, true) + rsrc, err := rh2.LookupHistorical(safeName, 3, true) if err != nil { teardownTest(t, err) } - // check data if !bytes.Equal(rsrc.data, []byte("clyde")) { teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("clyde"))) } + log.Debug("Historical lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data) // specific block, specific version - rsrc, err = rh2.LookupVersion(domainName, 3, 1, true) + rsrc, err = rh2.LookupVersion(safeName, 3, 1, true) if err != nil { teardownTest(t, err) } - // check data if !bytes.Equal(rsrc.data, []byte("inky")) { teardownTest(t, fmt.Errorf("resource data (historical) was %v, expected %v", rh2.resources[domainName].data, []byte("inky"))) } + log.Debug("Specific version lookup", "period", rh2.resources[safeName].lastPeriod, "version", rh2.resources[safeName].version, "data", rh2.resources[safeName].data) teardownTest(t, nil) } @@ -266,7 +271,7 @@ func TestResourceENSOwner(t *testing.T) { transactOpts := bind.NewKeyedTransactor(signer.privKey) // set up ENS sim - domainparts := strings.Split(domainName, ".") + domainparts := strings.Split(safeName, ".") contractAddr, contractbackend, err := setupENS(addr, transactOpts, domainparts[0], domainparts[1]) if err != nil { t.Fatal(err) @@ -284,14 +289,14 @@ func TestResourceENSOwner(t *testing.T) { } // create new resource when we are owner = ok - _, err = rh.NewResource(domainName, resourceFrequency, true) + _, err = rh.NewResource(safeName, resourceFrequency) if err != nil { teardownTest(t, fmt.Errorf("Create resource fail: %v", err)) } data := []byte("foo") // update resource when we are owner = ok - _, err = rh.Update(domainName, data) + _, err = rh.Update(safeName, data) if err != nil { teardownTest(t, fmt.Errorf("Update resource fail: %v", err)) } @@ -302,7 +307,7 @@ func TestResourceENSOwner(t *testing.T) { teardownTest(t, err) } rh.validator.(*ENSValidator).signFunc = signertwo.signContent - _, err = rh.Update(domainName, data) + _, err = rh.Update(safeName, data) if err == nil { teardownTest(t, fmt.Errorf("Expected resource update fail due to owner mismatch")) } @@ -331,15 +336,6 @@ func setupTest(contractbackend bind.ContractBackend, validator ResourceValidator } } - if validator == nil { - // create a new signer, which creates the private key - signer, err = newTestSigner() - if err != nil { - return - } - validator = NewGenericValidator(testHashFunc, signer.signContent) - } - // temp datadir datadir, err = ioutil.TempDir("", "rh") if err != nil { @@ -431,12 +427,7 @@ func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string, return contractAddress, contractBackend, nil } -func testHashFunc(name string) common.Hash { - testHasher.Reset() - testHasher.Write([]byte(name)) - return common.BytesToHash(testHasher.Sum(nil)) -} - +// implementation of an external signer to pass to validator type testSigner struct { privKey *ecdsa.PrivateKey hasher SwarmHash @@ -453,6 +444,7 @@ func newTestSigner() (*testSigner, error) { }, nil } +// matches the SignFunc type func (self *testSigner) signContent(data common.Hash) (signature Signature, err error) { signaturebytes, err := crypto.Sign(data.Bytes(), self.privKey) if err != nil { @@ -473,3 +465,31 @@ func (c *testCloudStore) Deliver(*Chunk) { func (c *testCloudStore) Retrieve(*Chunk) { } + +// Default fallthrough validation of mutable resource ownership +type testValidator struct { + *baseValidator + hashFunc func(string) common.Hash +} + +func newTestValidator(signFunc SignFunc) *testValidator { + return &testValidator{ + baseValidator: &baseValidator{ + signFunc: signFunc, + }, + hashFunc: func(name string) common.Hash { + testHasher.Reset() + testHasher.Write([]byte(name)) + return common.BytesToHash(testHasher.Sum(nil)) + }, + } + +} + +func (self *testValidator) checkAccess(name string, address common.Address) (bool, error) { + return true, nil +} + +func (self *testValidator) nameHash(name string) common.Hash { + return self.hashFunc(name) +}