Repository: theupdateframework/go-tuf
Pull request #196 (2021-12-10 23:05:43)

[Delegations prereq 6] Use a verify.DB for delegation in client
Splitting up https://github.com/theupdateframework/go-tuf/pull/175

This PR removes the type `verify.DelegationsVerifier` in favor of using a `verify.DB` directly to store sets of keys. This simplifies the types, since a `DelegationsVerifier` just wrapped a `DB` without adding significant functionality, and allows us to use the same type (a `verify.DB`) to hold keys for the top level targets role as for delegated targets roles.

coveralls: ## Pull Request Test Coverage Report for [Build 1565531878](https://coveralls.io/builds/44928251)

* **22** of **24**   **(91.67%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.06%**) to **70.129%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [internal/targets/delegation.go](https://coveralls.io/builds/44928251/source?filename=internal%2Ftargets%2Fdelegation.go#L27) | 11 | 13 | 84.62%
<!-- | **Total:** | **22** | **24** | **91.67%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/44928251/badge)](https://coveralls.io/builds/44928251) |
| :-- | --: |
| Change from base [Build 1565518152](https://coveralls.io/builds/44928148): |  -0.06% |
| Covered Lines: | 2127 |
| Relevant Lines: | 3033 |

---
##### 💛  - [Coveralls](https://coveralls.io)

hosseinsia: Would you please add some description to this PR?

Patch:
diff --git a/client/client.go b/client/client.go
index b4ea7f9..893e161 100644
--- a/client/client.go
+++ b/client/client.go
@@ -111,6 +111,8 @@ func NewClient(local LocalStore, remote RemoteStore) *Client {
 // The latest root.json is fetched from remote storage, verified using rootKeys
 // and threshold, and then saved in local storage. It is expected that rootKeys
 // were securely distributed with the software being updated.
+//
+// Deprecated: Use c.InitLocal and c.Update to initialize a local repository.
 func (c *Client) Init(rootKeys []*data.PublicKey, threshold int) error {
 	if len(rootKeys) < threshold {
 		return ErrInsufficientKeys
@@ -147,6 +149,20 @@ func (c *Client) Init(rootKeys []*data.PublicKey, threshold int) error {
 	return c.local.SetMeta("root.json", rootJSON)
 }
 
+// InitLocal initializes a local repository from root metadata.
+//
+// The root's keys are extracted from the root and saved in local storage.
+// Root expiration is not checked.
+// It is expected that rootJSON was securely distributed with the software
+// being updated.
+func (c *Client) InitLocal(rootJSON []byte) error {
+	err := c.loadAndVerifyRootMeta(rootJSON, true /*ignoreExpiredCheck*/)
+	if err != nil {
+		return err
+	}
+	return c.local.SetMeta("root.json", rootJSON)
+}
+
 // Update downloads and verifies remote metadata and returns updated targets.
 // It always performs root update (5.2 and 5.3) section of the v1.0.19 spec.
 //
@@ -430,6 +446,12 @@ func (c *Client) loadAndVerifyLocalRootMeta(ignoreExpiredCheck bool) error {
 	if !ok {
 		return ErrNoRootKeys
 	}
+	return c.loadAndVerifyRootMeta(rootJSON, ignoreExpiredCheck)
+}
+
+// loadAndVerifyRootMeta decodes and verifies root metadata and loads the top-level keys.
+// This method first clears the DB for top-level keys and then loads the new keys.
+func (c *Client) loadAndVerifyRootMeta(rootJSON []byte, ignoreExpiredCheck bool) error {
 	// unmarshal root.json without verifying as we need the root
 	// keys first
 	s := &data.Signed{}
diff --git a/client/delegations.go b/client/delegations.go
index cecab3a..f1ec67a 100644
--- a/client/delegations.go
+++ b/client/delegations.go
@@ -20,7 +20,11 @@ func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) {
 	// - filter delegations with paths or path_hash_prefixes matching searched target
 	// - 5.6.7.1 cycles protection
 	// - 5.6.7.2 terminations
-	delegations := targets.NewDelegationsIterator(target)
+	delegations, err := targets.NewDelegationsIterator(target, c.db)
+	if err != nil {
+		return data.TargetFileMeta{}, err
+	}
+
 	for i := 0; i < c.MaxDelegations; i++ {
 		d, ok := delegations.Next()
 		if !ok {
@@ -28,7 +32,7 @@ func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) {
 		}
 
 		// covers 5.6.{1,2,3,4,5,6}
-		targets, err := c.loadDelegatedTargets(snapshot, d.Delegatee.Name, d.Verifier)
+		targets, err := c.loadDelegatedTargets(snapshot, d)
 		if err != nil {
 			return data.TargetFileMeta{}, err
 		}
@@ -39,11 +43,11 @@ func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) {
 		}
 
 		if targets.Delegations != nil {
-			delegationsVerifier, err := verify.NewDelegationsVerifier(targets.Delegations)
+			delegationsDB, err := verify.NewDBFromDelegations(targets.Delegations)
 			if err != nil {
 				return data.TargetFileMeta{}, err
 			}
-			err = delegations.Add(targets.Delegations.Roles, d.Delegatee.Name, delegationsVerifier)
+			err = delegations.Add(targets.Delegations.Roles, d.Delegatee.Name, delegationsDB)
 			if err != nil {
 				return data.TargetFileMeta{}, err
 			}
@@ -75,7 +79,9 @@ func (c *Client) loadLocalSnapshot() (*data.Snapshot, error) {
 }
 
 // loadDelegatedTargets downloads, decodes, verifies and stores targets
-func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, verifier verify.DelegationsVerifier) (*data.Targets, error) {
+func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, delegation targets.Delegation) (*data.Targets, error) {
+	role := delegation.Delegatee.Name
+
 	var err error
 	fileName := role + ".json"
 	fileMeta, ok := snapshot.Meta[fileName]
@@ -98,11 +104,7 @@ func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, veri
 	// 5.6.3 verify signature with parent public keys
 	// 5.6.5 verify that the targets is not expired
 	// role "targets" is a top role verified by root keys loaded in the client db
-	if role == "targets" {
-		err = c.db.Unmarshal(raw, targets, role, fileMeta.Version)
-	} else {
-		err = verifier.Unmarshal(raw, targets, role, fileMeta.Version)
-	}
+	err = delegation.DB.Unmarshal(raw, targets, role, fileMeta.Version)
 	if err != nil {
 		return nil, ErrDecodeFailed{fileName, err}
 	}
diff --git a/cmd/tuf-client/init.go b/cmd/tuf-client/init.go
index 6b1296a..f043c0b 100644
--- a/cmd/tuf-client/init.go
+++ b/cmd/tuf-client/init.go
@@ -1,28 +1,26 @@
 package main
 
 import (
-	"encoding/json"
 	"io"
 	"os"
 
 	"github.com/flynn/go-docopt"
 	tuf "github.com/theupdateframework/go-tuf/client"
-	"github.com/theupdateframework/go-tuf/data"
 )
 
 func init() {
 	register("init", cmdInit, `
-usage: tuf-client init [-s|--store=<path>] <url> [<root-keys-file>]
+usage: tuf-client init [-s|--store=<path>] <url> [<root-metadata-file>]
 
 Options:
   -s <path>    The path to the local file store [default: tuf.db]
 
-Initialize the local file store with root keys.
+Initialize the local file store with root metadata.
   `)
 }
 
 func cmdInit(args *docopt.Args, client *tuf.Client) error {
-	file := args.String["<root-keys-file>"]
+	file := args.String["<root-metadata-file>"]
 	var in io.Reader
 	if file == "" || file == "-" {
 		in = os.Stdin
@@ -33,9 +31,9 @@ func cmdInit(args *docopt.Args, client *tuf.Client) error {
 			return err
 		}
 	}
-	var rootKeys []*data.PublicKey
-	if err := json.NewDecoder(in).Decode(&rootKeys); err != nil {
+	bytes, err := io.ReadAll(in)
+	if err != nil {
 		return err
 	}
-	return client.Init(rootKeys, len(rootKeys))
+	return client.InitLocal(bytes)
 }
diff --git a/go.mod b/go.mod
index c28a363..c28a0d0 100644
--- a/go.mod
+++ b/go.mod
@@ -5,10 +5,12 @@ go 1.16
 require (
 	github.com/dustin/go-humanize v1.0.0
 	github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e
-	github.com/secure-systems-lab/go-securesystemslib v0.3.0
+	github.com/onsi/gomega v1.18.1 // indirect
+	github.com/secure-systems-lab/go-securesystemslib v0.3.1
 	github.com/stretchr/testify v1.7.0
-	github.com/syndtr/goleveldb v1.0.0
+	github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
 	golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
+	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
-	gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
 )
diff --git a/go.sum b/go.sum
index e72e3ee..5e9074e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,57 +1,137 @@
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e h1:Ss/B3/5wWRh8+emnK0++g5zQzwDTi30W10pKxKc4JXI=
 github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e/go.mod h1:HyVoz1Mz5Co8TFO8EupIdlcpwShBmY98dkT2xeHkvEI=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
-github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/secure-systems-lab/go-securesystemslib v0.3.0 h1:PH0mUKuUSXVEVDbrKMgGPcrqrnKA8gJii614+EKKi7g=
-github.com/secure-systems-lab/go-securesystemslib v0.3.0/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U=
+github.com/secure-systems-lab/go-securesystemslib v0.3.1 h1:LJuyMziazadwmQRRu1M7GMUo5S1oH1+YxU9FjuSFU8k=
+github.com/secure-systems-lab/go-securesystemslib v0.3.1/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
-github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/targets/delegation.go b/pkg/targets/delegation.go
index 8e09c05..ccd52ba 100644
--- a/pkg/targets/delegation.go
+++ b/pkg/targets/delegation.go
@@ -1,14 +1,17 @@
 package targets
 
 import (
+	"errors"
+
 	"github.com/theupdateframework/go-tuf/data"
+	"github.com/theupdateframework/go-tuf/internal/sets"
 	"github.com/theupdateframework/go-tuf/verify"
 )
 
 type Delegation struct {
 	Delegator string
-	Verifier  verify.DelegationsVerifier
 	Delegatee data.DelegatedRole
+	DB        *verify.DB
 }
 
 type delegationsIterator struct {
@@ -17,19 +20,31 @@ type delegationsIterator struct {
 	visitedRoles map[string]struct{}
 }
 
+var ErrTopLevelTargetsRoleMissing = errors.New("tuf: top level targets role missing from top level keys DB")
+
 // NewDelegationsIterator initialises an iterator with a first step
-// on top level targets
-func NewDelegationsIterator(target string) *delegationsIterator {
+// on top level targets.
+func NewDelegationsIterator(target string, topLevelKeysDB *verify.DB) (*delegationsIterator, error) {
+	targetsRole := topLevelKeysDB.GetRole("targets")
+	if targetsRole == nil {
+		return nil, ErrTopLevelTargetsRoleMissing
+	}
+
 	i := &delegationsIterator{
 		target: target,
 		stack: []Delegation{
 			{
-				Delegatee: data.DelegatedRole{Name: "targets"},
+				Delegatee: data.DelegatedRole{
+					Name:      "targets",
+					KeyIDs:    sets.StringSetToSlice(targetsRole.KeyIDs),
+					Threshold: targetsRole.Threshold,
+				},
+				DB: topLevelKeysDB,
 			},
 		},
 		visitedRoles: make(map[string]struct{}),
 	}
-	return i
+	return i, nil
 }
 
 func (d *delegationsIterator) Next() (value Delegation, ok bool) {
@@ -57,7 +72,7 @@ func (d *delegationsIterator) Next() (value Delegation, ok bool) {
 	return delegation, true
 }
 
-func (d *delegationsIterator) Add(roles []data.DelegatedRole, delegator string, verifier verify.DelegationsVerifier) error {
+func (d *delegationsIterator) Add(roles []data.DelegatedRole, delegator string, db *verify.DB) error {
 	for i := len(roles) - 1; i >= 0; i-- {
 		// Push the roles onto the stack in reverse so we get an preorder traversal
 		// of the delegations graph.
@@ -70,7 +85,7 @@ func (d *delegationsIterator) Add(roles []data.DelegatedRole, delegator string,
 			delegation := Delegation{
 				Delegator: delegator,
 				Delegatee: r,
-				Verifier:  verifier,
+				DB:        db,
 			}
 			d.stack = append(d.stack, delegation)
 		}
diff --git a/verify/db.go b/verify/db.go
index f809490..a14a514 100644
--- a/verify/db.go
+++ b/verify/db.go
@@ -28,37 +28,28 @@ func NewDB() *DB {
 	}
 }
 
-type DelegationsVerifier struct {
-	DB *DB
-}
-
-func (d *DelegationsVerifier) Unmarshal(b []byte, v interface{}, role string, minVersion int) error {
-	return d.DB.Unmarshal(b, v, role, minVersion)
-}
-
-// NewDelegationsVerifier returns a DelegationsVerifier that verifies delegations
-// of a given Targets. It reuses the DB struct to leverage verified keys, roles
-// unmarshals.
-func NewDelegationsVerifier(d *data.Delegations) (DelegationsVerifier, error) {
+// NewDBFromDelegations returns a DB that verifies delegations
+// of a given Targets.
+func NewDBFromDelegations(d *data.Delegations) (*DB, error) {
 	db := &DB{
 		roles:     make(map[string]*Role, len(d.Roles)),
 		verifiers: make(map[string]keys.Verifier, len(d.Keys)),
 	}
 	for _, r := range d.Roles {
 		if _, ok := roles.TopLevelRoles[r.Name]; ok {
-			return DelegationsVerifier{}, ErrInvalidDelegatedRole
+			return nil, ErrInvalidDelegatedRole
 		}
 		role := &data.Role{Threshold: r.Threshold, KeyIDs: r.KeyIDs}
 		if err := db.addRole(r.Name, role); err != nil {
-			return DelegationsVerifier{}, err
+			return nil, err
 		}
 	}
 	for id, k := range d.Keys {
 		if err := db.AddKey(id, k); err != nil {
-			return DelegationsVerifier{}, err
+			return nil, err
 		}
 	}
-	return DelegationsVerifier{db}, nil
+	return db, nil
 }
 
 func (db *DB) AddKey(id string, k *data.PublicKey) error {


Test patch:
diff --git a/client/client_test.go b/client/client_test.go
index c0be244..5ad38b3 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -280,6 +280,50 @@ func (s *ClientSuite) TestInit(c *C) {
 	c.Assert(err, IsNil)
 }
 
+func (s *ClientSuite) TestInitLocalAllowsExpired(c *C) {
+	s.genKeyExpired(c, "targets")
+	c.Assert(s.repo.Snapshot(), IsNil)
+	c.Assert(s.repo.Timestamp(), IsNil)
+	c.Assert(s.repo.Commit(), IsNil)
+	s.syncRemote(c)
+	client := NewClient(MemoryLocalStore(), s.remote)
+	bytes, err := io.ReadAll(s.remote.meta["root.json"])
+	c.Assert(err, IsNil)
+	s.withMetaExpired(func() {
+		c.Assert(client.InitLocal(bytes), IsNil)
+	})
+}
+
+func (s *ClientSuite) TestInitLocal(c *C) {
+	client := NewClient(MemoryLocalStore(), s.remote)
+	bytes, err := io.ReadAll(s.remote.meta["root.json"])
+	c.Assert(err, IsNil)
+	dataSigned := &data.Signed{}
+	c.Assert(json.Unmarshal(bytes, dataSigned), IsNil)
+	root := &data.Root{}
+	c.Assert(json.Unmarshal(dataSigned.Signed, root), IsNil)
+
+	// check Update() returns ErrNoRootKeys when uninitialized
+	_, err = client.Update()
+	c.Assert(err, Equals, ErrNoRootKeys)
+
+	// check InitLocal() returns ErrInvalid when the root's signature is
+	// invalid
+	// modify root and marshal without regenerating signatures
+	root.Version = root.Version + 1
+	rootBytes, err := json.Marshal(root)
+	c.Assert(err, IsNil)
+	dataSigned.Signed = rootBytes
+	dataBytes, err := json.Marshal(dataSigned)
+	c.Assert(err, IsNil)
+	c.Assert(client.InitLocal(dataBytes), Equals, verify.ErrInvalid)
+
+	// check Update() does not return ErrNoRootKeys after initialization
+	c.Assert(client.InitLocal(bytes), IsNil)
+	_, err = client.Update()
+	c.Assert(err, IsNil)
+}
+
 func (s *ClientSuite) TestFirstUpdate(c *C) {
 	files, err := s.newClient(c).Update()
 	c.Assert(err, IsNil)
diff --git a/client/delegations_test.go b/client/delegations_test.go
index 13625d1..fe1d72b 100644
--- a/client/delegations_test.go
+++ b/client/delegations_test.go
@@ -1,6 +1,7 @@
 package client
 
 import (
+	"crypto/sha256"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -26,7 +27,13 @@ func TestGetTargetMeta(t *testing.T) {
 
 	f, err := c.getTargetFileMeta("f.txt")
 	assert.Nil(t, err)
-	assert.Equal(t, int64(15), f.Length)
+	hash := sha256.Sum256([]byte("Contents: f.txt"))
+	assert.Equal(t, data.HexBytes(hash[:]), f.Hashes["sha256"])
+
+	f, err = c.getTargetFileMeta("targets.txt")
+	assert.Nil(t, err)
+	hash = sha256.Sum256([]byte("Contents: targets.txt"))
+	assert.Equal(t, data.HexBytes(hash[:]), f.Hashes["sha256"])
 }
 
 func TestMaxDelegations(t *testing.T) {
@@ -268,19 +275,7 @@ func initTestDelegationClient(t *testing.T, dirPrefix string) (*Client, func() e
 	c := NewClient(MemoryLocalStore(), remote)
 	rawFile, err := ioutil.ReadFile(initialStateDir + "/" + "root.json")
 	assert.Nil(t, err)
-	s := &data.Signed{}
-	root := &data.Root{}
-	assert.Nil(t, json.Unmarshal(rawFile, s))
-	assert.Nil(t, json.Unmarshal(s.Signed, root))
-	var keys []*data.PublicKey
-	for _, sig := range s.Signatures {
-		k, ok := root.Keys[sig.KeyID]
-		if ok {
-			keys = append(keys, k)
-		}
-	}
-
-	assert.Nil(t, c.Init(keys, 1))
+	assert.Nil(t, c.InitLocal(rawFile))
 	files, err := ioutil.ReadDir(initialStateDir)
 	assert.Nil(t, err)
 
diff --git a/client/interop_test.go b/client/interop_test.go
index dfe90e9..19a8d9b 100644
--- a/client/interop_test.go
+++ b/client/interop_test.go
@@ -2,8 +2,8 @@ package client
 
 import (
 	"bytes"
-	"encoding/json"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"net"
 	"net/http"
@@ -11,7 +11,6 @@ import (
 	"path/filepath"
 	"strconv"
 
-	"github.com/theupdateframework/go-tuf/data"
 	"github.com/theupdateframework/go-tuf/util"
 	. "gopkg.in/check.v1"
 
@@ -150,9 +149,12 @@ func (t *testCase) runStep(c *C, stepName string) {
 	c.Assert(err, IsNil)
 
 	client := NewClient(t.local, remote)
-	// initiate a client with the root keys
-	keys := getKeys(c, remote)
-	c.Assert(client.Init(keys, 1), IsNil)
+	// initiate a client with the root metadata
+	ioReader, _, err := remote.GetMeta("root.json")
+	c.Assert(err, IsNil)
+	rootJsonBytes, err := io.ReadAll(ioReader)
+	c.Assert(err, IsNil)
+	c.Assert(client.InitLocal(rootJsonBytes), IsNil)
 
 	// check update returns the correct updated targets
 	files, err := client.Update()
@@ -191,31 +193,3 @@ func startFileServer(c *C, dir string) (string, func() error) {
 	go http.Serve(l, http.FileServer(http.Dir(dir)))
 	return addr, l.Close
 }
-
-func getKeys(c *C, remote RemoteStore) []*data.PublicKey {
-	r, _, err := remote.GetMeta("root.json")
-	c.Assert(err, IsNil)
-
-	type SignedRoot struct {
-		Signed data.Root
-	}
-	root := &SignedRoot{}
-	err = json.NewDecoder(r).Decode(&root)
-	c.Assert(err, IsNil)
-
-	rootRole, exists := root.Signed.Roles["root"]
-	c.Assert(exists, Equals, true)
-
-	rootKeys := []*data.PublicKey{}
-
-	for _, keyID := range rootRole.KeyIDs {
-		key, exists := root.Signed.Keys[keyID]
-		c.Assert(exists, Equals, true)
-
-		rootKeys = append(rootKeys, key)
-	}
-
-	c.Assert(rootKeys, Not(HasLen), 0)
-
-	return rootKeys
-}
diff --git a/client/python_interop/python_interop_test.go b/client/python_interop/python_interop_test.go
index 590f456..7a4abdc 100644
--- a/client/python_interop/python_interop_test.go
+++ b/client/python_interop/python_interop_test.go
@@ -2,7 +2,6 @@ package client
 
 import (
 	"bytes"
-	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net"
@@ -14,10 +13,7 @@ import (
 
 	tuf "github.com/theupdateframework/go-tuf"
 	client "github.com/theupdateframework/go-tuf/client"
-	"github.com/theupdateframework/go-tuf/data"
-	"github.com/theupdateframework/go-tuf/pkg/keys"
 	"github.com/theupdateframework/go-tuf/util"
-	"golang.org/x/crypto/ed25519"
 	. "gopkg.in/check.v1"
 )
 
@@ -59,17 +55,11 @@ func (InteropSuite) TestGoClientPythonGenerated(c *C) {
 		)
 		c.Assert(err, IsNil)
 
-		// initiate a client with the root keys
-		f, err := os.Open(filepath.Join(testDataDir, dir, "keystore", "root_key.pub"))
-		c.Assert(err, IsNil)
-		key := &data.PublicKey{}
-		c.Assert(json.NewDecoder(f).Decode(key), IsNil)
-		c.Assert(key.Type, Equals, "ed25519")
-		pk, err := keys.GetVerifier(key)
-		c.Assert(err, IsNil)
-		c.Assert(pk.Public(), HasLen, ed25519.PublicKeySize)
+		// initiate a client with the root metadata
 		client := client.NewClient(client.MemoryLocalStore(), remote)
-		c.Assert(client.Init([]*data.PublicKey{key}, 1), IsNil)
+		rootJSON, err := ioutil.ReadFile(filepath.Join(testDataDir, dir, "repository", "metadata", "root.json"))
+		c.Assert(err, IsNil)
+		c.Assert(client.InitLocal(rootJSON), IsNil)
 
 		// check update returns the correct updated targets
 		files, err := client.Update()
diff --git a/client/testdata/go-tuf-transition-M3/generate.go b/client/testdata/go-tuf-transition-M3/generate.go
index 1d35e1b..20cb8dc 100644
--- a/client/testdata/go-tuf-transition-M3/generate.go
+++ b/client/testdata/go-tuf-transition-M3/generate.go
@@ -22,7 +22,7 @@ type persistedKeys struct {
 	Data      []*data.PrivateKey `json:"data"`
 }
 
-func assertNotNil(err error) {
+func assertNoError(err error) {
 	if err != nil {
 		panic(fmt.Sprintf("assertion failed: %s", err))
 	}
@@ -30,31 +30,31 @@ func assertNotNil(err error) {
 
 func copyRepo(src string, dst string) {
 	cmd := exec.Command("cp", "-r", src, dst)
-	assertNotNil(cmd.Run())
+	assertNoError(cmd.Run())
 }
 
 func newRepo(dir string) *tuf.Repo {
 	repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
-	assertNotNil(err)
+	assertNoError(err)
 
 	return repo
 }
 
 func commit(dir string, repo *tuf.Repo) {
-	assertNotNil(repo.SnapshotWithExpires(expirationDate))
-	assertNotNil(repo.TimestampWithExpires(expirationDate))
-	assertNotNil(repo.Commit())
+	assertNoError(repo.SnapshotWithExpires(expirationDate))
+	assertNoError(repo.TimestampWithExpires(expirationDate))
+	assertNoError(repo.Commit())
 
 	// Remove the keys directory to make sure we don't accidentally use a key.
-	assertNotNil(os.RemoveAll(filepath.Join(dir, "keys")))
+	assertNoError(os.RemoveAll(filepath.Join(dir, "keys")))
 }
 
 func addKeys(repo *tuf.Repo, roleKeys map[string][]*data.PrivateKey) {
 	for role, keyList := range roleKeys {
 		for _, key := range keyList {
 			signer, err := keys.GetSigner(key)
-			assertNotNil(err)
-			assertNotNil(repo.AddPrivateKeyWithExpires(role, signer, expirationDate))
+			assertNoError(err)
+			assertNoError(repo.AddPrivateKeyWithExpires(role, signer, expirationDate))
 		}
 	}
 }
@@ -63,27 +63,27 @@ func addTargets(repo *tuf.Repo, dir string, files map[string][]byte) {
 	paths := []string{}
 	for file, data := range files {
 		path := filepath.Join(dir, "staged", "targets", file)
-		assertNotNil(os.MkdirAll(filepath.Dir(path), 0755))
-		assertNotNil(ioutil.WriteFile(path, data, 0644))
+		assertNoError(os.MkdirAll(filepath.Dir(path), 0755))
+		assertNoError(ioutil.WriteFile(path, data, 0644))
 		paths = append(paths, file)
 	}
-	assertNotNil(repo.AddTargetsWithExpires(paths, nil, expirationDate))
+	assertNoError(repo.AddTargetsWithExpires(paths, nil, expirationDate))
 }
 
 func revokeKeys(repo *tuf.Repo, role string, keyList []*data.PrivateKey) {
 	for _, key := range keyList {
 		signer, err := keys.GetSigner(key)
-		assertNotNil(err)
-		assertNotNil(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
+		assertNoError(err)
+		assertNoError(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
 	}
 }
 
 func generateRepos(dir string, consistentSnapshot bool) {
 	f, err := os.Open("../keys.json")
-	assertNotNil(err)
+	assertNoError(err)
 
 	var roleKeys map[string][][]*data.PrivateKey
-	assertNotNil(json.NewDecoder(f).Decode(&roleKeys))
+	assertNoError(json.NewDecoder(f).Decode(&roleKeys))
 
 	// Collect all the initial keys we'll use when creating repositories.
 	// We'll modify this to reflect rotated keys.
@@ -140,7 +140,7 @@ func generateRepos(dir string, consistentSnapshot bool) {
 
 func main() {
 	cwd, err := os.Getwd()
-	assertNotNil(err)
+	assertNoError(err)
 
 	for _, consistentSnapshot := range []bool{false, true} {
 		name := fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot)
diff --git a/client/testdata/go-tuf-transition-M4/generate.go b/client/testdata/go-tuf-transition-M4/generate.go
index 7e6d5f5..552474c 100644
--- a/client/testdata/go-tuf-transition-M4/generate.go
+++ b/client/testdata/go-tuf-transition-M4/generate.go
@@ -22,7 +22,7 @@ type persistedKeys struct {
 	Data      []*data.PrivateKey `json:"data"`
 }
 
-func assertNotNil(err error) {
+func assertNoError(err error) {
 	if err != nil {
 		panic(fmt.Sprintf("assertion failed: %s", err))
 	}
@@ -30,29 +30,29 @@ func assertNotNil(err error) {
 
 func copyRepo(src string, dst string) {
 	cmd := exec.Command("cp", "-r", src, dst)
-	assertNotNil(cmd.Run())
+	assertNoError(cmd.Run())
 }
 
 func newRepo(dir string) *tuf.Repo {
 	repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
-	assertNotNil(err)
+	assertNoError(err)
 
 	return repo
 }
 
 func commit(dir string, repo *tuf.Repo) {
-	assertNotNil(repo.SnapshotWithExpires(expirationDate))
-	assertNotNil(repo.TimestampWithExpires(expirationDate))
-	assertNotNil(repo.Commit())
+	assertNoError(repo.SnapshotWithExpires(expirationDate))
+	assertNoError(repo.TimestampWithExpires(expirationDate))
+	assertNoError(repo.Commit())
 
 	// Remove the keys directory to make sure we don't accidentally use a key.
-	assertNotNil(os.RemoveAll(filepath.Join(dir, "keys")))
+	assertNoError(os.RemoveAll(filepath.Join(dir, "keys")))
 }
 
 func addKeys(repo *tuf.Repo, roleKeys map[string][]*data.PrivateKey) {
 	for role, keys := range roleKeys {
 		for _, key := range keys {
-			assertNotNil(repo.AddPrivateKeyWithExpires(role, key, expirationDate))
+			assertNoError(repo.AddPrivateKeyWithExpires(role, key, expirationDate))
 		}
 	}
 }
@@ -61,27 +61,27 @@ func addTargets(repo *tuf.Repo, dir string, files map[string][]byte) {
 	paths := []string{}
 	for file, data := range files {
 		path := filepath.Join(dir, "staged", "targets", file)
-		assertNotNil(os.MkdirAll(filepath.Dir(path), 0755))
-		assertNotNil(ioutil.WriteFile(path, data, 0644))
+		assertNoError(os.MkdirAll(filepath.Dir(path), 0755))
+		assertNoError(ioutil.WriteFile(path, data, 0644))
 		paths = append(paths, file)
 	}
-	assertNotNil(repo.AddTargetsWithExpires(paths, nil, expirationDate))
+	assertNoError(repo.AddTargetsWithExpires(paths, nil, expirationDate))
 }
 
 func revokeKeys(repo *tuf.Repo, role string, keyList []*data.PrivateKey) {
 	for _, key := range keyList {
 		signer, err := keys.GetSigner(key)
-		assertNotNil(err)
-		assertNotNil(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
+		assertNoError(err)
+		assertNoError(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
 	}
 }
 
 func generateRepos(dir string, consistentSnapshot bool) {
 	f, err := os.Open("../keys.json")
-	assertNotNil(err)
+	assertNoError(err)
 
 	var roleKeys map[string][][]*data.PrivateKey
-	assertNotNil(json.NewDecoder(f).Decode(&roleKeys))
+	assertNoError(json.NewDecoder(f).Decode(&roleKeys))
 
 	// Collect all the initial keys we'll use when creating repositories.
 	// We'll modify this to reflect rotated keys.
@@ -138,7 +138,7 @@ func generateRepos(dir string, consistentSnapshot bool) {
 
 func main() {
 	cwd, err := os.Getwd()
-	assertNotNil(err)
+	assertNoError(err)
 
 	for _, consistentSnapshot := range []bool{false, true} {
 		name := fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot)
diff --git a/client/testdata/go-tuf/generator/generator.go b/client/testdata/go-tuf/generator/generator.go
index 93c2bb8..eca808f 100644
--- a/client/testdata/go-tuf/generator/generator.go
+++ b/client/testdata/go-tuf/generator/generator.go
@@ -22,7 +22,7 @@ type persistedKeys struct {
 	Data      []*data.PrivateKey `json:"data"`
 }
 
-func assertNotNil(err error) {
+func assertNoError(err error) {
 	if err != nil {
 		panic(fmt.Sprintf("assertion failed: %s", err))
 	}
@@ -30,31 +30,31 @@ func assertNotNil(err error) {
 
 func copyRepo(src string, dst string) {
 	cmd := exec.Command("cp", "-r", src, dst)
-	assertNotNil(cmd.Run())
+	assertNoError(cmd.Run())
 }
 
 func newRepo(dir string) *tuf.Repo {
 	repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
-	assertNotNil(err)
+	assertNoError(err)
 
 	return repo
 }
 
 func commit(dir string, repo *tuf.Repo) {
-	assertNotNil(repo.SnapshotWithExpires(expirationDate))
-	assertNotNil(repo.TimestampWithExpires(expirationDate))
-	assertNotNil(repo.Commit())
+	assertNoError(repo.SnapshotWithExpires(expirationDate))
+	assertNoError(repo.TimestampWithExpires(expirationDate))
+	assertNoError(repo.Commit())
 
 	// Remove the keys directory to make sure we don't accidentally use a key.
-	assertNotNil(os.RemoveAll(filepath.Join(dir, "keys")))
+	assertNoError(os.RemoveAll(filepath.Join(dir, "keys")))
 }
 
 func addKeys(repo *tuf.Repo, roleKeys map[string][]*data.PrivateKey) {
 	for role, keyList := range roleKeys {
 		for _, key := range keyList {
 			signer, err := keys.GetSigner(key)
-			assertNotNil(err)
-			assertNotNil(repo.AddPrivateKeyWithExpires(role, signer, expirationDate))
+			assertNoError(err)
+			assertNoError(repo.AddPrivateKeyWithExpires(role, signer, expirationDate))
 		}
 	}
 }
@@ -63,18 +63,18 @@ func addTargets(repo *tuf.Repo, dir string, files map[string][]byte) {
 	paths := []string{}
 	for file, data := range files {
 		path := filepath.Join(dir, "staged", "targets", file)
-		assertNotNil(os.MkdirAll(filepath.Dir(path), 0755))
-		assertNotNil(ioutil.WriteFile(path, data, 0644))
+		assertNoError(os.MkdirAll(filepath.Dir(path), 0755))
+		assertNoError(ioutil.WriteFile(path, data, 0644))
 		paths = append(paths, file)
 	}
-	assertNotNil(repo.AddTargetsWithExpires(paths, nil, expirationDate))
+	assertNoError(repo.AddTargetsWithExpires(paths, nil, expirationDate))
 }
 
 func revokeKeys(repo *tuf.Repo, role string, keyList []*data.PrivateKey) {
 	for _, key := range keyList {
 		signer, err := keys.GetSigner(key)
-		assertNotNil(err)
-		assertNotNil(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
+		assertNoError(err)
+		assertNoError(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
 	}
 }
 
@@ -134,10 +134,10 @@ func generateRepos(dir string, roleKeys map[string][][]*data.PrivateKey, consist
 
 func Generate(dir string, keysPath string, consistentSnapshot bool) {
 	f, err := os.Open(keysPath)
-	assertNotNil(err)
+	assertNoError(err)
 
 	var roleKeys map[string][][]*data.PrivateKey
-	assertNotNil(json.NewDecoder(f).Decode(&roleKeys))
+	assertNoError(json.NewDecoder(f).Decode(&roleKeys))
 
 	log.Printf("generating %s", dir)
 
diff --git a/client/testdata/tools/gen-keys.go b/client/testdata/tools/gen-keys.go
index 896fc01..f592d15 100644
--- a/client/testdata/tools/gen-keys.go
+++ b/client/testdata/tools/gen-keys.go
@@ -30,7 +30,7 @@ func main() {
 
 		for i := 0; i < 2; i++ {
 			signer, err := keys.GenerateEd25519Key()
-			assertNotNil(err)
+			assertNoError(err)
 			keys = append(keys, []*data.PrivateKey{signer})
 		}
 
@@ -38,12 +38,12 @@ func main() {
 	}
 
 	s, err := json.MarshalIndent(&roles, "", "    ")
-	assertNotNil(err)
+	assertNoError(err)
 
 	ioutil.WriteFile("keys.json", []byte(s), 0644)
 }
 
-func assertNotNil(err error) {
+func assertNoError(err error) {
 	if err != nil {
 		panic(fmt.Sprintf("assertion failed: %s", err))
 	}
diff --git a/pkg/targets/delegation_test.go b/pkg/targets/delegation_test.go
index bb460b1..2e0c42b 100644
--- a/pkg/targets/delegation_test.go
+++ b/pkg/targets/delegation_test.go
@@ -14,6 +14,20 @@ var (
 )
 
 func TestDelegationsIterator(t *testing.T) {
+	topTargetsPubKey := &data.PublicKey{
+		Type:       data.KeyTypeEd25519,
+		Scheme:     data.KeySchemeEd25519,
+		Algorithms: data.HashAlgorithms,
+		Value:      []byte(`{"public":"aaaaec567e5901ba3976c34f7cd5169704292439bf71e6aa19c64b96706f95ef"}`),
+	}
+	delTargetsPubKey := &data.PublicKey{
+		Type:       data.KeyTypeEd25519,
+		Scheme:     data.KeySchemeEd25519,
+		Algorithms: data.HashAlgorithms,
+		Value:      []byte(`{"public":"bbbbec567e5901ba3976c34f7cd5169704292439bf71e6aa19c64b96706f95ef"}`),
+	}
+
+	defaultKeyIDs := delTargetsPubKey.IDs()
 	var iteratorTests = []struct {
 		testName    string
 		roles       map[string][]data.DelegatedRole
@@ -21,27 +35,35 @@ func TestDelegationsIterator(t *testing.T) {
 		resultOrder []string
 		err         error
 	}{
+		{
+			testName: "no delegation",
+			roles: map[string][]data.DelegatedRole{
+				"targets": {},
+			},
+			file:        "test.txt",
+			resultOrder: []string{"targets"},
+		},
 		{
 			testName: "no termination",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "b", Paths: defaultPathPatterns},
-					{Name: "e", Paths: defaultPathPatterns},
+					{Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"b": {
-					{Name: "c", Paths: defaultPathPatterns},
+					{Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"c": {
-					{Name: "d", Paths: defaultPathPatterns},
+					{Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"e": {
-					{Name: "f", Paths: defaultPathPatterns},
-					{Name: "g", Paths: defaultPathPatterns},
+					{Name: "f", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "g", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"g": {
-					{Name: "h", Paths: defaultPathPatterns},
-					{Name: "i", Paths: defaultPathPatterns},
-					{Name: "j", Paths: defaultPathPatterns},
+					{Name: "h", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "i", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "j", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 			},
 			file:        "",
@@ -51,12 +73,12 @@ func TestDelegationsIterator(t *testing.T) {
 			testName: "terminated in b",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "b", Paths: defaultPathPatterns, Terminating: true},
-					{Name: "e", Paths: defaultPathPatterns},
+					{Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs, Terminating: true},
+					{Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"b": {
-					{Name: "c", Paths: defaultPathPatterns},
-					{Name: "d", Paths: defaultPathPatterns},
+					{Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 			},
 			file:        "",
@@ -66,12 +88,12 @@ func TestDelegationsIterator(t *testing.T) {
 			testName: "path does not match b",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "b", Paths: noMatchPathPatterns},
-					{Name: "e", Paths: defaultPathPatterns},
+					{Name: "b", Paths: noMatchPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"b": {
-					{Name: "c", Paths: defaultPathPatterns},
-					{Name: "d", Paths: defaultPathPatterns},
+					{Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 			},
 			file:        "",
@@ -81,12 +103,13 @@ func TestDelegationsIterator(t *testing.T) {
 			testName: "path does not match b - path prefixes",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "b", PathHashPrefixes: []string{"33472a4909"}},
-					{Name: "c", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633"}},
+					{Name: "b", PathHashPrefixes: []string{"33472a4909"}, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "c", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633"}, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"c": {
-					{Name: "d", PathHashPrefixes: []string{"8baf"}},
-					{Name: "e", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633472a49096ed87f8f764bd597831eac371f40ac39"}},
+
+					{Name: "d", PathHashPrefixes: []string{"8baf"}, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "e", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633472a49096ed87f8f764bd597831eac371f40ac39"}, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 			},
 			file:        "/e/f/g.txt",
@@ -96,7 +119,7 @@ func TestDelegationsIterator(t *testing.T) {
 			testName: "err paths and pathHashPrefixes are set",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "b", Paths: defaultPathPatterns, PathHashPrefixes: defaultPathPatterns},
+					{Name: "b", Paths: defaultPathPatterns, PathHashPrefixes: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"b": {},
 			},
@@ -108,48 +131,54 @@ func TestDelegationsIterator(t *testing.T) {
 			testName: "cycle avoided 1",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "b", Paths: defaultPathPatterns},
-					{Name: "e", Paths: defaultPathPatterns},
+					{Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+				},
+				"a": {
+					{Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"b": {
-					{Name: "targets", Paths: defaultPathPatterns},
-					{Name: "d", Paths: defaultPathPatterns},
+					{Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 			},
 			file:        "",
-			resultOrder: []string{"targets", "b", "d", "e"},
+			resultOrder: []string{"targets", "a", "b", "d", "e"},
 		},
 		{
 			testName: "cycle avoided 2",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "targets", Paths: defaultPathPatterns},
-					{Name: "b", Paths: defaultPathPatterns},
+					{Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+				},
+				"a": {
+					{Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"b": {
-					{Name: "targets", Paths: defaultPathPatterns},
-					{Name: "b", Paths: defaultPathPatterns},
-					{Name: "c", Paths: defaultPathPatterns},
+					{Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"c": {
-					{Name: "c", Paths: defaultPathPatterns},
+					{Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 			},
 			file:        "",
-			resultOrder: []string{"targets", "b", "c"},
+			resultOrder: []string{"targets", "a", "b", "c"},
 		},
 		{
 			testName: "diamond delegation",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "b", Paths: defaultPathPatterns},
-					{Name: "c", Paths: defaultPathPatterns},
+					{Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+					{Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"b": {
-					{Name: "d", Paths: defaultPathPatterns},
+					{Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"c": {
-					{Name: "d", Paths: defaultPathPatterns},
+					{Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 			},
 			file:        "",
@@ -159,10 +188,10 @@ func TestDelegationsIterator(t *testing.T) {
 			testName: "simple cycle",
 			roles: map[string][]data.DelegatedRole{
 				"targets": {
-					{Name: "a", Paths: defaultPathPatterns},
+					{Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 				"a": {
-					{Name: "a", Paths: defaultPathPatterns},
+					{Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
 				},
 			},
 			file:        "",
@@ -172,22 +201,52 @@ func TestDelegationsIterator(t *testing.T) {
 
 	for _, tt := range iteratorTests {
 		t.Run(tt.testName, func(t *testing.T) {
-			d := NewDelegationsIterator(tt.file)
+			topLevelDB := verify.NewDB()
+			topLevelDB.AddKey(topTargetsPubKey.IDs()[0], topTargetsPubKey)
+			topLevelDB.AddRole("targets", &data.Role{
+				KeyIDs:    topTargetsPubKey.IDs(),
+				Threshold: 1,
+			})
+
+			d, err := NewDelegationsIterator(tt.file, topLevelDB)
+			assert.NoError(t, err)
+
 			var iterationOrder []string
 			for {
 				r, ok := d.Next()
 				if !ok {
 					break
 				}
+
+				// A delegation should have associated keys. Testing the exact keys
+				// isn't useful in this module since the keys are supplied by the
+				// caller in the arguments to Add().
+				assert.Greater(t, len(r.Delegatee.KeyIDs), 0)
+
 				iterationOrder = append(iterationOrder, r.Delegatee.Name)
 				delegations, ok := tt.roles[r.Delegatee.Name]
 				if !ok {
 					continue
 				}
-				err := d.Add(delegations, r.Delegatee.Name, verify.DelegationsVerifier{})
+
+				db, err := verify.NewDBFromDelegations(&data.Delegations{
+					Roles: delegations,
+				})
+				assert.NoError(t, err)
+
+				err = d.Add(delegations, r.Delegatee.Name, db)
 				assert.Equal(t, tt.err, err)
 			}
 			assert.Equal(t, tt.resultOrder, iterationOrder)
 		})
 	}
 }
+
+func TestNewDelegationsIteratorError(t *testing.T) {
+	// Empty DB. It is supposed to have at least the top-level targets role and
+	// keys.
+	tldb := verify.NewDB()
+
+	_, err := NewDelegationsIterator("targets", tldb)
+	assert.ErrorIs(t, err, ErrTopLevelTargetsRoleMissing)
+}
diff --git a/verify/db_test.go b/verify/db_test.go
index cb32898..e4bab0a 100644
--- a/verify/db_test.go
+++ b/verify/db_test.go
@@ -7,8 +7,8 @@ import (
 	"github.com/theupdateframework/go-tuf/data"
 )
 
-func TestDelegationsVerifier(t *testing.T) {
-	var verifierTests = []struct {
+func TestDelegationsDB(t *testing.T) {
+	var dbTests = []struct {
 		testName     string
 		delegations  *data.Delegations
 		initErr      error
@@ -42,14 +42,14 @@ func TestDelegationsVerifier(t *testing.T) {
 		},
 	}
 
-	for _, tt := range verifierTests {
+	for _, tt := range dbTests {
 		t.Run(tt.testName, func(t *testing.T) {
-			verifier, err := NewDelegationsVerifier(tt.delegations)
-			assert.NotNil(t, verifier)
+			db, err := NewDBFromDelegations(tt.delegations)
 			assert.Equal(t, tt.initErr, err)
 			if err == nil {
+				assert.NotNil(t, db)
 				var targets data.Targets
-				err = verifier.Unmarshal([]byte(`{"a":"b"}`), targets, "tree", 0)
+				err = db.Unmarshal([]byte(`{"a":"b"}`), targets, "tree", 0)
 				assert.Equal(t, tt.unmarshalErr, err)
 			}
 		})