summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 17:39:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 17:39:05 +0000
commitd3911883df1e317b23fa12be7e1c7b45f74d630a (patch)
tree154cebf32f39b26b8a88e9bb359c57288d629e3b
parentInitial commit. (diff)
downloadgolang-github-theupdateframework-go-tuf-upstream.tar.xz
golang-github-theupdateframework-go-tuf-upstream.zip
Adding upstream version 0.6.1.upstream/0.6.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.gitattributes5
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md18
-rw-r--r--.github/dependabot.yml32
-rw-r--r--.github/workflows/changelog.yml18
-rw-r--r--.github/workflows/ci.yml25
-rw-r--r--.github/workflows/codeql-analysis.yml70
-rw-r--r--.github/workflows/release.yml32
-rw-r--r--.github/workflows/specification-version-check.yml14
-rw-r--r--.github/workflows/tests.yml74
-rw-r--r--.gitignore5
-rw-r--r--.golangci.yml10
-rw-r--r--.goreleaser/tuf-client.yml40
-rw-r--r--.goreleaser/tuf.yml40
-rw-r--r--LICENSE27
-rw-r--r--README.md662
-rw-r--r--client/client.go989
-rw-r--r--client/client_test.go1668
-rw-r--r--client/delegations.go152
-rw-r--r--client/delegations_test.go316
-rw-r--r--client/errors.go107
-rw-r--r--client/file_store.go90
-rw-r--r--client/file_store_test.go197
-rw-r--r--client/filejsonstore/filejsonstore.go148
-rw-r--r--client/filejsonstore/filejsonstore_test.go194
-rw-r--r--client/filejsonstore/perm_test.go54
-rw-r--r--client/interop_test.go201
-rw-r--r--client/leveldbstore/leveldbstore.go60
-rw-r--r--client/leveldbstore/leveldbstore_test.go123
-rw-r--r--client/local_store.go29
-rw-r--r--client/local_store_test.go29
-rw-r--r--client/python_interop/python_interop_test.go236
-rw-r--r--client/python_interop/testdata/LICENSE.txt66
-rw-r--r--client/python_interop/testdata/Makefile10
-rw-r--r--client/python_interop/testdata/README.md47
-rw-r--r--client/python_interop/testdata/python-tuf-v3.0.0/Dockerfile7
-rw-r--r--client/python_interop/testdata/python-tuf-v3.0.0/client.py68
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/generate.py138
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/generate.sh40
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.root.json71
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.snapshot.json19
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.targets.json28
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/timestamp.json19
-rw-r--r--client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/targets/55ae75d991c770d8f3ef07cbfde124ffce9c420da5db6203afab700b27e10cf9.file1.txt1
-rw-r--r--client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/targets/dir/04e2f59431a9d219321baf7d21b8cc797d7615dc3e9515c782c49d2075658701.file2.txt1
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/1.root.json71
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/snapshot.json19
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/targets.json28
-rwxr-xr-xclient/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/timestamp.json19
-rw-r--r--client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/targets/dir/file2.txt1
-rw-r--r--client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/targets/file1.txt1
-rw-r--r--client/remote_store.go109
-rw-r--r--client/testdata/Published1Time/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published1Time/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published1Time/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published1Time/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published1Time/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published1Time/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published1Time/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published1Time/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published1Time/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published1Time/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published1Time/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published1Time/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published1Time/hash.txt1
-rw-r--r--client/testdata/Published1Time/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published1Time/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published1Time/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time/server/metadata.staged/root.json87
-rw-r--r--client/testdata/Published1Time/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published1Time/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published1Time/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published1Time/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published1Time/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published1Time/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time/server/metadata/root.json87
-rw-r--r--client/testdata/Published1Time/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published1Time/server/metadata/targets.json19
-rw-r--r--client/testdata/Published1Time/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/hash.txt1
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/root.json87
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata/2.root.json87
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata/root.json87
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata/targets.json19
-rw-r--r--client/testdata/Published1Time_backwardRootVersion/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_no_root/hash.txt1
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata.staged/root.json87
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata/root.json87
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata/targets.json19
-rw-r--r--client/testdata/Published1Time_client_no_root/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_root_only/hash.txt1
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata.staged/root.json87
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata/root.json87
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata/targets.json19
-rw-r--r--client/testdata/Published1Time_client_root_only/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated/hash.txt1
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/2.root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/hash.txt1
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/2.root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/hash.txt1
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/2.root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/hash.txt1
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/2.root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/root.json103
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/targets.json19
-rw-r--r--client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published2Times_roottoolarge/hash.txt1
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/root.json103
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/2.root.jsonbin0 -> 512001 bytes
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/root.json103
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/targets.json19
-rw-r--r--client/testdata/Published2Times_roottoolarge/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/hash.txt1
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.root.json99
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.snapshot.json23
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/root.json99
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/snapshot.json23
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.root.json99
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.snapshot.json23
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.timestamp.json24
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/root.json99
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/snapshot.json23
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/targets.json19
-rw-r--r--client/testdata/Published2Times_snapshot_keyrotated/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/hash.txt1
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.root.json99
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.targets.json23
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/root.json99
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/targets.json23
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/2.root.json99
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/2.snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/2.targets.json23
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/2.timestamp.json24
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/root.json99
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/targets.json23
-rw-r--r--client/testdata/Published2Times_targets_keyrotated/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/hash.txt1
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/2.root.json99
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/2.timestamp.json28
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/root.json99
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/timestamp.json28
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/2.root.json99
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/2.timestamp.json28
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/root.json99
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/targets.json19
-rw-r--r--client/testdata/Published2Times_timestamp_keyrotated/server/metadata/timestamp.json28
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/hash.txt1
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/3.root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/2.root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/hash.txt1
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/3.root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/3.root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/hash.txt1
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/3.root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/3.root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/hash.txt1
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/3.root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.root.json87
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/2.root.json103
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/3.root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/root.json119
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/snapshot.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/targets.json19
-rw-r--r--client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/hash.txt1
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/2.root.json191
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/root.json191
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/2.root.json191
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/root.json191
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/hash.txt1
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/2.root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/2.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/2.root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/2.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/hash.txt1
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/2.root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/2.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/2.root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/2.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/hash.txt1
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/2.root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/2.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/2.root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/2.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/root.json159
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/hash.txt1
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/2.root.json167
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/root.json167
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.root.json215
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/2.root.json167
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/root.json167
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/hash.txt1
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/2.root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/2.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/2.root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/2.snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/snapshot.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/hash.txt1
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/2.root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/2.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/2.root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/2.targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/targets.json51
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/timestamp.json24
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/hash.txt1
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/2.root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/2.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.root.json183
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/2.root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/2.timestamp.json56
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/root.json135
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/snapshot.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/targets.json19
-rw-r--r--client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/timestamp.json56
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/1.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/targets.json28
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/targets/01
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/1.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/2.root.json143
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/root.json143
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets.json34
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets/01
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets/11
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/2.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/3.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets.json40
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/11
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/21
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/3.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/4.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets.json46
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/11
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/21
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/31
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/4.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/5.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets.json52
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/11
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/31
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/41
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/4.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/5.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets.json58
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/11
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/31
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/41
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/51
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/timestamp.json29
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.targets.json28
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json28
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets.json28
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets/0.01
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json34
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.root.json143
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.targets.json34
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json36
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json143
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/root.json143
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets.json34
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/0.01
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/0.11
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json36
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.targets.json40
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json133
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json40
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets.json40
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.11
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json36
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.targets.json46
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json46
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets.json46
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.11
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.21
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.31
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.targets.json52
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json133
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json52
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/root.json133
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets.json52
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.11
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.31
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.41
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.41
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/timestamp.json29
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6.snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6.targets.json58
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/63c16cc2225e77b7c94ec3b143271e02d97a1bfba828e11820392b9108412ef22f09732694673331f386b6f8a281103e20d07c21db78f487e1b046d9968173c8.snapshot.json36
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/8ec9b98766edfe6acc23bb700183532a990b966ceb7b62784f6c80d4b1bfe5661df3cc2765b57bd0e78eb4b5c2b136f5ab7a365665c6e918d4137b83cb7e2262.targets.json58
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json1
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/root.json1
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/snapshot.json36
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets.json58
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.11
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.31
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.41
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.51
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.51
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
l---------client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.41
-rw-r--r--client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/timestamp.json29
-rw-r--r--client/testdata/go-tuf-transition-M3/generate.go150
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/1.root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/targets.json22
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/targets/01
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/1.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/2.root.json87
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/root.json87
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets.json28
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets/01
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets/11
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/2.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/3.root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets.json34
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/01
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/11
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/21
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/3.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/4.root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets.json40
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/01
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/11
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/21
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/31
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/4.root.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/5.root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets.json46
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/01
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/11
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/21
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/31
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/41
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/4.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/5.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/root.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets.json52
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/01
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/11
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/21
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/31
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/41
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/51
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/timestamp.json23
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.targets.json22
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/targets.json22
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.root.json87
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.targets.json28
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/root.json87
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets.json28
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.targets.json34
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets.json34
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.targets.json40
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets.json40
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.targets.json46
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/root.json83
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets.json46
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.41
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/timestamp.json23
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.targets.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.root.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.snapshot.json1
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.targets.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/6.snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/6.targets.json52
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/root.json1
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/snapshot.json30
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets.json52
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.51
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
l---------client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.41
-rw-r--r--client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/timestamp.json23
-rw-r--r--client/testdata/go-tuf-transition-M4/generate.go148
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/0/repository/1.root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/0/repository/root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/0/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/0/repository/targets.json22
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/0/repository/targets/01
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/0/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-false/1/repository/1.root.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/1/repository/2.root.json91
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/1/repository/root.json91
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/1/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets.json28
l---------client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets/01
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets/11
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/1/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-false/2/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/2/repository/2.root.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/2/repository/3.root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/2/repository/root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/2/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets.json34
l---------client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/01
l---------client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/11
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/21
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/2/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-false/3/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/3/repository/2.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/3/repository/3.root.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/3/repository/4.root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/3/repository/root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/3/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets.json40
l---------client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/01
l---------client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/11
l---------client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/21
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/31
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/3/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-false/4/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/4/repository/2.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/4/repository/3.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/4/repository/4.root.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/4/repository/5.root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/4/repository/root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/4/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets.json46
l---------client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/01
l---------client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/11
l---------client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/21
l---------client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/31
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/41
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/4/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/2.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/3.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/4.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/5.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/root.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/5/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets.json52
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/01
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/11
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/21
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/31
l---------client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/41
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/51
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-false/5/repository/timestamp.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.targets.json22
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/0/repository/root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/0/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/0/repository/targets.json22
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/0/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.targets.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.root.json91
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.targets.json28
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/1/repository/root.json91
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/1/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets.json28
l---------client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/1/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.targets.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.targets.json34
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/2/repository/root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/2/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets.json34
l---------client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/2/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.targets.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.targets.json40
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/3/repository/root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/3/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets.json40
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/3/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.targets.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.targets.json46
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/4/repository/root.json87
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/4/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets.json46
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.41
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/4/repository/timestamp.json23
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.targets.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.root.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.snapshot.json1
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.targets.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/5/repository/6.snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/5/repository/6.targets.json52
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/root.json1
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/5/repository/snapshot.json23
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets.json52
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.51
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.01
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.31
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.21
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.11
l---------client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.41
-rw-r--r--client/testdata/go-tuf/consistent-snapshot-true/5/repository/timestamp.json23
-rw-r--r--client/testdata/go-tuf/generate.go19
-rw-r--r--client/testdata/go-tuf/generator/generator.go177
-rwxr-xr-xclient/testdata/go-tuf/regenerate-metadata.sh14
-rw-r--r--client/testdata/keys.json122
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/__init__.py35
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.root.json87
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.snapshot.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.targets.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.timestamp.json24
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/root.json87
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/snapshot.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/targets.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/timestamp.json24
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.root.json87
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.snapshot.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.targets.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.timestamp.json24
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/root.json87
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/snapshot.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/targets.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/timestamp.json24
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.a.json74
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.b.json74
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.c.json28
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.d.json28
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.e.json28
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.f.json28
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.root.json87
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.snapshot.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.targets.json19
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.timestamp.json24
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.root.json87
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.snapshot.json37
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.targets.json73
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.timestamp.json24
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/a.json74
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/b.json74
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/c.json28
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/d.json28
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/e.json28
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/f.json28
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/root.json87
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/snapshot.json37
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/targets.json73
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/timestamp.json24
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/027b3d1814dae8547aed3d7e7b99aa0d59d82cc18411037b0ef0318eb754ce86.e.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/0951c719b5adf26a55700bd1ac6b657fb513b04e4adb335a935a0da7f12f2ac99834b47442d0b832e5f4ffdd365ef538f14d3a52221c0cf81ef7b68ce312476a.targets.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/3f90cedf303207851bbdc5f857e018daf93b4c0083306cef17df547b42e4e985.a.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/6da6f624811b0d7792c57dc9ed58f1af8cbf4ebfaaaa3ea2011b57b4af63f7e6.targets.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/79b44a183eaa0025f3997b856537502ac1759de74263985ad1dd9ab0bb5cc8ce46b6c45c1bf37adb87c2839cca54ae784c3d3488e994d29f003decc81638d589.d.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/946e2ea9180de673891ae09ce0edfb207966bc32bd6324ebdad2c50c82075ffd.c.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/949c6a4318dabe8bbd140cef99ea669ba031919ccf9bce0f5b4d0b61d1c0aa2e.b.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/a.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b2ce56612fa7e840d56d263f75804f60baaad5d6e0a7d544e65e1791f2df5e55.d.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b8061c4c83d478aaadc6758a74861f749420e83dd6bf07e931738b0619404a75d12758429b12f8d047643d6d13047ec0b65bb253cf5f5b961515658864301207.f.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/c.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/d.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/ddb5fb256a368d778b5fdd03d497ad79944c766245f3cccfc8b098b14c488ec424a68b86a6a2add36db4ef0f0214f15dbe0d63fbc5ca7a9619fb4c39544d78a9.c.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/e.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f0b07df15d4a3e98ffc6ee6a4c00d04623b0171de458d94dbec4abb7646bc60e.f.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f4631ef7ea7b015d7b88e411842fafeb78a72f0181bec72ea9754604ede74ea0e491bf8411659aabc96304fc764d0131ce49ba86066ab5f7b7480dde719e0bfd.a.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f4cc9ce5c73b37e2a6707af7a0ea614ea5fa428bd2509e3af4528a5d330ce98a09c4dd98c859ad9b27b8aba24e1eacbf1af8393fdbfed899cecb995c87a11e3c.b.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/ffca8c347335e5e019deba8a15212297fe25afc734ac06fcd01b4d9dae12b16d750d7a44bdafcada254148990aa6611bc9ed2f2841da2c0dd70bb870be7275f6.e.txt1
-rw-r--r--client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/targets.txt1
-rw-r--r--client/testdata/tools/gen-keys.go50
-rw-r--r--client/testdata/tools/linkify-metadata.go112
-rw-r--r--cmd/tuf-client/README.md48
-rw-r--r--cmd/tuf-client/get.go51
-rw-r--r--cmd/tuf-client/init.go39
-rw-r--r--cmd/tuf-client/list.go39
-rw-r--r--cmd/tuf-client/main.go98
-rw-r--r--cmd/tuf/add.go39
-rw-r--r--cmd/tuf/add_signatures.go42
-rw-r--r--cmd/tuf/change_passphrase.go22
-rw-r--r--cmd/tuf/clean.go26
-rw-r--r--cmd/tuf/commit.go18
-rw-r--r--cmd/tuf/gen_key.go64
-rw-r--r--cmd/tuf/get_threshold.go29
-rw-r--r--cmd/tuf/init.go23
-rw-r--r--cmd/tuf/main.go175
-rw-r--r--cmd/tuf/payload.go26
-rw-r--r--cmd/tuf/regenerate.go25
-rw-r--r--cmd/tuf/remove.go38
-rw-r--r--cmd/tuf/revoke_key.go31
-rw-r--r--cmd/tuf/root_keys.go33
-rw-r--r--cmd/tuf/set_threshold.go34
-rw-r--r--cmd/tuf/sign.go21
-rw-r--r--cmd/tuf/sign_payload.go41
-rw-r--r--cmd/tuf/snapshot.go31
-rw-r--r--cmd/tuf/status.go47
-rw-r--r--cmd/tuf/timestamp.go31
-rw-r--r--data/hex_bytes.go42
-rw-r--r--data/hex_bytes_test.go44
-rw-r--r--data/types.go348
-rw-r--r--data/types_test.go287
-rw-r--r--docs/ALUMNI4
-rw-r--r--docs/CODE_OF_CONDUCT.md4
-rw-r--r--docs/CONTRIBUTING.md62
-rw-r--r--docs/MAINTAINERS6
-rw-r--r--docs/MAINTAINERS.md57
-rw-r--r--docs/SECURITY.md21
-rw-r--r--docs/TESTING.md18
-rw-r--r--docs/audit/X41-go-tuf-Audit-2023-Final-Report-PUBLIC.pdfbin0 -> 319663 bytes
-rw-r--r--docs/audit/blog-post.md33
-rw-r--r--encrypted/encrypted.go294
-rw-r--r--encrypted/encrypted_test.go158
-rw-r--r--errors.go98
-rw-r--r--go.mod25
-rw-r--r--go.sum140
-rw-r--r--internal/fsutil/fsutil.go23
-rw-r--r--internal/fsutil/perm.go30
-rw-r--r--internal/fsutil/perm_test.go69
-rw-r--r--internal/fsutil/perm_windows.go17
-rw-r--r--internal/roles/roles.go48
-rw-r--r--internal/roles/roles_test.go54
-rw-r--r--internal/sets/strings.go24
-rw-r--r--internal/sets/strings_test.go35
-rw-r--r--internal/signer/sort.go49
-rw-r--r--internal/signer/sort_test.go73
-rw-r--r--local_store.go741
-rw-r--r--local_store_test.go70
-rw-r--r--pkg/deprecated/deprecated_repo_test.go107
-rw-r--r--pkg/deprecated/set_ecdsa/set_ecdsa.go26
-rw-r--r--pkg/keys/deprecated_ecdsa.go101
-rw-r--r--pkg/keys/deprecated_ecdsa_test.go129
-rw-r--r--pkg/keys/ecdsa.go173
-rw-r--r--pkg/keys/ecdsa_test.go163
-rw-r--r--pkg/keys/ed25519.go161
-rw-r--r--pkg/keys/ed25519_test.go99
-rw-r--r--pkg/keys/keys.go82
-rw-r--r--pkg/keys/keys_test.go39
-rw-r--r--pkg/keys/pkix.go56
-rw-r--r--pkg/keys/pkix_test.go62
-rw-r--r--pkg/keys/rsa.go162
-rw-r--r--pkg/keys/rsa_test.go125
-rw-r--r--pkg/targets/delegation.go102
-rw-r--r--pkg/targets/delegation_test.go252
-rw-r--r--pkg/targets/hash_bins.go113
-rw-r--r--pkg/targets/hash_bins_test.go119
-rw-r--r--repo.go1695
-rw-r--r--repo_test.go2752
-rw-r--r--requirements-test.txt5
-rw-r--r--sign/sign.go80
-rw-r--r--util/util.go332
-rw-r--r--util/util_test.go282
-rw-r--r--verify/db.go104
-rw-r--r--verify/db_test.go131
-rw-r--r--verify/errors.go73
-rw-r--r--verify/verify.go187
-rw-r--r--verify/verify_test.go302
1660 files changed, 76410 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..2fad43c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+# go enforces lf line endings
+*.go eol=lf
+
+# testdata should not be mangled by git
+*.json binary
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..56186b3
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,18 @@
+Please fill in the fields below to submit a pull request. The more information that is provided, the better.
+
+Fixes #<Issue>
+
+Release Notes: <!-- What comments/remarks should we include in the release notes for this change? -->
+
+**Types of changes**:
+<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected). **Please ensure that your PR title** is a [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) breaking change (with a `!`, as in `feat!: change foo`).
+
+**Description of the changes being introduced by the pull request**:
+
+**Please verify and check that the pull request fulfills the following requirements**:
+
+- [ ] Tests have been added for the bug fix or new feature
+- [ ] Docs have been added for the bug fix or new feature
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..840da33
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,32 @@
+version: 2
+updates:
+ # Monitor Go dependencies
+ - package-ecosystem: "gomod"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ time: "10:00"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+ open-pull-requests-limit: 10
+ # Monitor Github Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ time: "10:00"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+ open-pull-requests-limit: 10
+ # Monitor Python test dependencies
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ time: "10:00"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+ open-pull-requests-limit: 10
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
new file mode 100644
index 0000000..3d43051
--- /dev/null
+++ b/.github/workflows/changelog.yml
@@ -0,0 +1,18 @@
+name: "Changelog checks"
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - edited
+ - synchronize
+
+jobs:
+ conventional-commits-lint-check:
+ name: "Lint PR title"
+ runs-on: "ubuntu-latest"
+ steps:
+ - name:
+ uses: amannn/action-semantic-pull-request@c3cd5d1ea3580753008872425915e343e351ab54
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..295923e
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,25 @@
+on:
+ pull_request:
+ push:
+ branches:
+ - "master"
+ tags:
+ - "v*"
+name: CI
+permissions:
+ contents: write
+jobs:
+ tests:
+ uses: ./.github/workflows/tests.yml
+ tuf:
+ needs: tests
+ if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
+ uses: ./.github/workflows/release.yml
+ with:
+ cli-name: tuf
+ tuf-client:
+ needs: tests
+ if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
+ uses: ./.github/workflows/release.yml
+ with:
+ cli-name: tuf-client
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..056a925
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,70 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '25 14 * * 6'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'go' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..0b87659
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,32 @@
+on:
+ workflow_call:
+ inputs:
+ cli-name:
+ required: true
+ type: string
+name: Release
+jobs:
+ release:
+ permissions:
+ contents: write
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Get Go version
+ uses: arnested/go-version-action@13d01fa2f867e8a08e9f8197def5f2da5a11ab6d
+ id: go-version
+ - name: Set up Go
+ uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
+ with:
+ go-version: ${{ steps.go-version.outputs.minimal }}
+ - name: Run GoReleaser
+ uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7
+ with:
+ distribution: goreleaser
+ version: "v1.7.0"
+ args: release --config ./.goreleaser/${{ inputs.cli-name }}.yml --rm-dist
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/specification-version-check.yml b/.github/workflows/specification-version-check.yml
new file mode 100644
index 0000000..523e455
--- /dev/null
+++ b/.github/workflows/specification-version-check.yml
@@ -0,0 +1,14 @@
+on:
+ schedule:
+ - cron: "0 13 * * *"
+ workflow_dispatch:
+name: Specification version check
+jobs:
+ # Get the latest TUF specification release and open an issue (if needed)
+ specification-bump-check:
+ permissions:
+ contents: read
+ issues: write
+ uses: theupdateframework/specification/.github/workflows/check-latest-spec-version.yml@master
+ with:
+ tuf-version: "v1.0.32" # Should be updated to the according version either manually or extracted automatically as how it's done in python-tuf
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..d1ae975
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,74 @@
+on:
+ workflow_call:
+name: Tests
+jobs:
+ get-go-versions:
+ name: Collect available Go versions
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.versions.outputs.matrix }}
+ steps:
+ - uses: actions/checkout@v3
+ - uses: arnested/go-version-action@13d01fa2f867e8a08e9f8197def5f2da5a11ab6d
+ id: versions
+
+ run:
+ strategy:
+ fail-fast: false # Keep running if one leg fails.
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }}
+ runs-on: ${{ matrix.os }}
+ needs: get-go-versions
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Setup - Go ${{ matrix.go-version }}
+ uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
+ with:
+ go-version: ${{ matrix.go-version }}
+
+ - name: Setup - Python
+ uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1
+ with:
+ python-version: "3.10"
+ cache: "pip"
+ cache-dependency-path: "requirements-test.txt"
+
+ - name: Install Python dependencies
+ run: |
+ python3 -m pip install --upgrade pip
+ python3 -m pip install --upgrade -r requirements-test.txt
+
+ - name: Run tests
+ run: go test -race -covermode atomic -coverprofile='profile.cov' ./...
+
+ - name: Send coverage
+ uses: shogo82148/actions-goveralls@df920a6a2468668dfcf71a0b43817f89eaa5ea04
+ with:
+ path-to-profile: profile.cov
+ flag-name: Go-${{ matrix.go-version }}
+ parallel: true
+
+ checks:
+ strategy:
+ matrix:
+ go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }}
+ runs-on: ubuntu-latest
+ needs: get-go-versions
+ steps:
+ - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
+ with:
+ go-version: ${{ matrix.go-version }}
+ - uses: actions/checkout@v3
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299
+ with:
+ version: v1.51 # has to be pinned and thus manually updated due to https://github.com/golangci/golangci-lint-action/blob/6a290f7d5d488e1e423b0b37fe802c822ca2c08c/README.md?plain=1#L108
+ args: --timeout 5m --verbose
+ - name: govulncheck
+ uses: golang/govulncheck-action@v1
+ with:
+ go-version-input: ${{ matrix.go-version }}
+ go-package: -json ./...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..39e7149
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+cmd/tuf/tuf
+cmd/tuf-client/tuf-client
+.vscode
+*~
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..992c119
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,10 @@
+linters:
+ disable-all: true
+ enable:
+ - staticcheck
+ - gofmt
+ - govet
+ - gosimple
+ - unused
+ - typecheck
+ - forbidigo
diff --git a/.goreleaser/tuf-client.yml b/.goreleaser/tuf-client.yml
new file mode 100644
index 0000000..6682004
--- /dev/null
+++ b/.goreleaser/tuf-client.yml
@@ -0,0 +1,40 @@
+project_name: tuf-client
+builds:
+ - ldflags:
+ - "-s -w"
+ - "-extldflags=-zrelro"
+ - "-extldflags=-znow"
+ env:
+ - "CGO_ENABLED=0"
+ - "GO111MODULE=on"
+ flags:
+ - -mod=readonly
+ - -trimpath
+ goos:
+ - linux
+ - darwin
+ - windows
+ goarch:
+ - amd64
+ - arm64
+ main: ./cmd/tuf-client/
+source:
+ enabled: true
+changelog:
+ use: github
+ groups:
+ - title: "Breaking changes"
+ regexp: "^.*BREAKING CHANGE[(\\w)]*:+.*$"
+ order: 0
+ - title: Features
+ regexp: "^.*feat[(\\w)]*:+.*$"
+ order: 1
+ - title: "Bug fixes"
+ regexp: "^.*fix[(\\w)]*:+.*$"
+ order: 2
+ - title: Others
+ order: 999
+release:
+ # If set to auto, will mark the release as not ready for production
+ # in case there is an indicator for this in the tag e.g. v1.0.0-rc1
+ prerelease: auto
diff --git a/.goreleaser/tuf.yml b/.goreleaser/tuf.yml
new file mode 100644
index 0000000..394ebc8
--- /dev/null
+++ b/.goreleaser/tuf.yml
@@ -0,0 +1,40 @@
+project_name: tuf
+builds:
+ - ldflags:
+ - "-s -w"
+ - "-extldflags=-zrelro"
+ - "-extldflags=-znow"
+ env:
+ - "CGO_ENABLED=0"
+ - "GO111MODULE=on"
+ flags:
+ - -mod=readonly
+ - -trimpath
+ goos:
+ - linux
+ - darwin
+ - windows
+ goarch:
+ - amd64
+ - arm64
+ main: ./cmd/tuf/
+source:
+ enabled: true
+changelog:
+ use: github
+ groups:
+ - title: "Breaking changes"
+ regexp: "^.*(?:BREAKING CHANGE)|![(\\w)]*:+.*$"
+ order: 0
+ - title: Features
+ regexp: "^.*feat[(\\w)]*:+.*$"
+ order: 1
+ - title: "Bug fixes"
+ regexp: "^.*fix[(\\w)]*:+.*$"
+ order: 2
+ - title: Others
+ order: 999
+release:
+ # If set to auto, will mark the release as not ready for production
+ # in case there is an indicator for this in the tag e.g. v1.0.0-rc1
+ prerelease: auto
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..38163dd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2014-2020 Prime Directive, Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Prime Directive, Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fe28367
--- /dev/null
+++ b/README.md
@@ -0,0 +1,662 @@
+# go-tuf
+
+[![build](https://github.com/theupdateframework/go-tuf/workflows/build/badge.svg)](https://github.com/theupdateframework/go-tuf/actions?query=workflow%3Abuild) [![Coverage Status](https://coveralls.io/repos/github/theupdateframework/go-tuf/badge.svg)](https://coveralls.io/github/theupdateframework/go-tuf) [![PkgGoDev](https://pkg.go.dev/badge/github.com/theupdateframework/go-tuf)](https://pkg.go.dev/github.com/theupdateframework/go-tuf) [![Go Report Card](https://goreportcard.com/badge/github.com/theupdateframework/go-tuf)](https://goreportcard.com/report/github.com/theupdateframework/go-tuf)
+
+This is a Go implementation of [The Update Framework (TUF)](http://theupdateframework.com/),
+a framework for securing software update systems.
+
+## Directory layout
+
+A TUF repository has the following directory layout:
+
+```bash
+.
+├── keys
+├── repository
+│   └── targets
+└── staged
+    └── targets
+```
+
+The directories contain the following files:
+
+- `keys/` - signing keys (optionally encrypted) with filename pattern `ROLE.json`
+- `repository/` - signed metadata files
+- `repository/targets/` - hashed target files
+- `staged/` - either signed, unsigned or partially signed metadata files
+- `staged/targets/` - unhashed target files
+
+## CLI
+
+`go-tuf` provides a CLI for managing a local TUF repository.
+
+### Install
+
+`go-tuf` is tested on Go versions 1.18.
+
+```bash
+go install github.com/theupdateframework/go-tuf/cmd/tuf@latest
+```
+
+### Commands
+
+#### `tuf init [--consistent-snapshot=false]`
+
+Initializes a new repository.
+
+This is only required if the repository should not generate consistent
+snapshots (i.e. by passing `--consistent-snapshot=false`). If consistent
+snapshots should be generated, the repository will be implicitly
+initialized to do so when generating keys.
+
+#### `tuf gen-key [--expires=<days>] <role>`
+
+Prompts the user for an encryption passphrase (unless the
+`--insecure-plaintext` flag is set), then generates a new signing key and
+writes it to the relevant key file in the `keys` directory. It also stages
+the addition of the new key to the `root` metadata file. Alternatively, passphrases
+can be set via environment variables in the form of `TUF_{{ROLE}}_PASSPHRASE`
+
+#### `tuf revoke-key [--expires=<days>] <role> <id>`
+
+Revoke a signing key
+
+The key will be removed from the root metadata file, but the key will remain in the
+"keys" directory if present.
+
+#### `tuf add [<path>...]`
+
+Hashes files in the `staged/targets` directory at the given path(s), then
+updates and stages the `targets` metadata file. Specifying no paths hashes all
+files in the `staged/targets` directory.
+
+#### `tuf remove [<path>...]`
+
+Stages the removal of files with the given path(s) from the `targets` metadata file
+(they get removed from the filesystem when the change is committed). Specifying
+no paths removes all files from the `targets` metadata file.
+
+#### `tuf snapshot [--expires=<days>]`
+
+Expects a staged, fully signed `targets` metadata file and stages an appropriate
+`snapshot` metadata file. Optionally one can set number of days after which
+the `snapshot` metadata will expire.
+
+#### `tuf timestamp [--expires=<days>]`
+
+Stages an appropriate `timestamp` metadata file. If a `snapshot` metadata file is staged,
+it must be fully signed. Optionally one can set number of days after which
+the timestamp metadata will expire.
+
+#### `tuf sign <metadata>`
+
+Signs the given role's staged metadata file with all keys present in the `keys`
+directory for that role.
+
+#### `tuf commit`
+
+Verifies that all staged changes contain the correct information and are signed
+to the correct threshold, then moves the staged files into the `repository`
+directory. It also removes any target files which are not in the `targets`
+metadata file.
+
+#### `tuf regenerate [--consistent-snapshot=false]`
+
+Note: Not supported yet
+
+Recreates the `targets` metadata file based on the files in `repository/targets`.
+
+#### `tuf clean`
+
+Removes all staged metadata files and targets.
+
+#### `tuf root-keys`
+
+Outputs a JSON serialized array of root keys to STDOUT. The resulting JSON
+should be distributed to clients for performing initial updates.
+
+#### `tuf set-threshold <role> <threshold>`
+
+Sets `role`'s threshold (required number of keys for signing) to
+`threshold`.
+
+#### `tuf get-threshold <role>`
+
+Outputs `role`'s threshold (required number of keys for signing).
+
+#### `tuf change-passphrase <role>`
+
+Changes the passphrase for given role keys file. The CLI supports reading
+both the existing and the new passphrase via the following environment
+variables - `TUF_{{ROLE}}_PASSPHRASE` and respectively `TUF_NEW_{{ROLE}}_PASSPHRASE`
+
+#### `tuf payload <metadata>`
+
+Outputs the metadata file for a role in a ready-to-sign (canonicalized) format.
+
+See also `tuf sign-payload` and `tuf add-signatures`.
+
+#### `tuf sign-payload --role=<role> <path>`
+
+Sign a file (outside of the TUF repo) using keys (in the TUF keys database,
+typically produced by `tuf gen-key`) for the given `role` (from the TUF repo).
+
+Typically, `path` will be a file containing the output of `tuf payload`.
+
+See also `tuf add-signatures`.
+
+#### `tuf add-signatures --signatures <sig_file> <metadata>`
+
+
+Adds signatures (the output of `tuf sign-payload`) to the given role metadata file.
+
+If the signature does not verify, it will not be added.
+
+#### `tuf status --valid-at <date> <role>`
+
+Check if the role's metadata will be expired on the given date.
+
+#### Usage of environment variables
+
+The `tuf` CLI supports receiving passphrases via environment variables in
+the form of `TUF_{{ROLE}}_PASSPHRASE` for existing ones and
+`TUF_NEW_{{ROLE}}_PASSPHRASE` for setting new ones.
+
+For a list of supported commands, run `tuf help` from the command line.
+
+### Examples
+
+The following are example workflows for managing a TUF repository with the CLI.
+
+The `tree` commands do not need to be run, but their output serve as an
+illustration of what files should exist after performing certain commands.
+
+Although only two machines are referenced (i.e. the "root" and "repo" boxes),
+the workflows can be trivially extended to many signing machines by copying
+staged changes and signing on each machine in turn before finally committing.
+
+Some key IDs are truncated for illustrative purposes.
+
+#### Create signed root metadata file
+
+Generate a root key on the root box:
+
+```bash
+$ tuf gen-key root
+Enter root keys passphrase:
+Repeat root keys passphrase:
+Generated root key with ID 184b133f
+
+$ tree .
+.
+├── keys
+│   └── root.json
+├── repository
+└── staged
+ ├── root.json
+ └── targets
+```
+
+Copy `staged/root.json` from the root box to the repo box and generate targets,
+snapshot and timestamp keys:
+
+```bash
+$ tree .
+.
+├── keys
+├── repository
+└── staged
+ ├── root.json
+ └── targets
+
+$ tuf gen-key targets
+Enter targets keys passphrase:
+Repeat targets keys passphrase:
+Generated targets key with ID 8cf4810c
+
+$ tuf gen-key snapshot
+Enter snapshot keys passphrase:
+Repeat snapshot keys passphrase:
+Generated snapshot key with ID 3e070e53
+
+$ tuf gen-key timestamp
+Enter timestamp keys passphrase:
+Repeat timestamp keys passphrase:
+Generated timestamp key with ID a3768063
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+└── staged
+ ├── root.json
+ └── targets
+```
+
+Copy `staged/root.json` from the repo box back to the root box and sign it:
+
+```bash
+$ tree .
+.
+├── keys
+│   ├── root.json
+├── repository
+└── staged
+ ├── root.json
+ └── targets
+
+$ tuf sign root.json
+Enter root keys passphrase:
+```
+
+The staged `root.json` can now be copied back to the repo box ready to be
+committed alongside other metadata files.
+
+#### Alternate signing flow
+
+Instead of manually copying `root.json` into the TUF repository on the root box,
+you can use the `tuf payload`, `tuf sign-payload`, `tuf add-signatures` flow.
+
+On the repo box, get the `root.json` payload in a canonical format:
+
+``` bash
+$ tuf payload root.json > root.json.payload
+```
+
+Copy `root.json.payload` to the root box and sign it:
+
+
+``` bash
+$ tuf sign-payload --role=root root.json.payload > root.json.sigs
+Enter root keys passphrase:
+```
+
+Copy `root.json.sigs` back to the repo box and import the signatures:
+
+``` bash
+$ tuf add-signatures --signatures root.json.sigs root.json
+```
+
+This achieves the same state as the above flow for the repo box:
+
+```bash
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+└── staged
+ ├── root.json
+ └── targets
+```
+
+#### Add a target file
+
+Assuming a staged, signed `root` metadata file and the file to add exists at
+`staged/targets/foo/bar/baz.txt`:
+
+```bash
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+└── staged
+    ├── root.json
+ └── targets
+ └── foo
+ └── bar
+ └── baz.txt
+
+$ tuf add foo/bar/baz.txt
+Enter targets keys passphrase:
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+└── staged
+    ├── root.json
+ ├── targets
+ │   └── foo
+ │   └── bar
+ │   └── baz.txt
+ └── targets.json
+
+$ tuf snapshot
+Enter snapshot keys passphrase:
+
+$ tuf timestamp
+Enter timestamp keys passphrase:
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+└── staged
+    ├── root.json
+    ├── snapshot.json
+ ├── targets
+ │   └── foo
+ │   └── bar
+ │   └── baz.txt
+    ├── targets.json
+    └── timestamp.json
+
+$ tuf commit
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+```
+
+#### Remove a target file
+
+Assuming the file to remove is at `repository/targets/foo/bar/baz.txt`:
+
+```bash
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+
+$ tuf remove foo/bar/baz.txt
+Enter targets keys passphrase:
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+ └── targets.json
+
+$ tuf snapshot
+Enter snapshot keys passphrase:
+
+$ tuf timestamp
+Enter timestamp keys passphrase:
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+    ├── snapshot.json
+    ├── targets.json
+    └── timestamp.json
+
+$ tuf commit
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+```
+
+#### Regenerate metadata files based on targets tree (Note: Not supported yet)
+
+```bash
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+
+$ tuf regenerate
+Enter targets keys passphrase:
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+ └── targets.json
+
+$ tuf snapshot
+Enter snapshot keys passphrase:
+
+$ tuf timestamp
+Enter timestamp keys passphrase:
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+    ├── snapshot.json
+    ├── targets.json
+    └── timestamp.json
+
+$ tuf commit
+
+$ tree .
+.
+├── keys
+│   ├── snapshot.json
+│   ├── targets.json
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+```
+
+#### Update timestamp.json
+
+```bash
+$ tree .
+.
+├── keys
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+
+$ tuf timestamp
+Enter timestamp keys passphrase:
+
+$ tree .
+.
+├── keys
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+ └── timestamp.json
+
+$ tuf commit
+
+$ tree .
+.
+├── keys
+│   └── timestamp.json
+├── repository
+│   ├── root.json
+│   ├── snapshot.json
+│   ├── targets
+│   │   └── foo
+│   │   └── bar
+│   │   └── baz.txt
+│   ├── targets.json
+│   └── timestamp.json
+└── staged
+```
+
+#### Adding a new root key
+
+Copy `staged/root.json` to the root box and generate a new root key on the root box:
+
+```bash
+$ tuf gen-key root
+$ tuf sign root.json
+```
+
+Copy `staged/root.json` from the root box and commit:
+
+```bash
+$ tuf commit
+```
+
+#### Rotating root key(s)
+
+Copy `staged/root.json` to the root box to do the rotation, where `abcd` is the keyid of the key that is being replaced:
+
+```bash
+$ tuf gen-key root
+$ tuf revoke-key root abcd
+$ tuf sign root.json
+```
+
+Note that `revoke-key` removes the old key from `root.json`, but the key remains in the `keys/` directory on the root box as it is needed to sign the next `root.json`. After this signing is done, the old key may be removed from `keys/`. Any number of keys may be added or revoked during this step, but ensure that at least a threshold of valid keys remain.
+
+Copy `staged/root.json` from the root box to commit:
+
+```bash
+$ tuf commit
+```
+
+## Client
+
+For the client package, see https://godoc.org/github.com/theupdateframework/go-tuf/client.
+
+For the client CLI, see https://github.com/theupdateframework/go-tuf/tree/master/cmd/tuf-client.
+
+## Contributing and Development
+
+For local development, `go-tuf` requires Go version 1.18.
+
+The [Python interoperability tests](client/python_interop/) require Python 3
+(available as `python` on the `$PATH`) and the [`python-tuf`
+package](https://github.com/theupdateframework/python-tuf) installed (`pip
+install tuf`). To update the data for these tests requires Docker and make (see
+test data [README.md](client/python_interop/testdata/README.md) for details).
+
+Please see [CONTRIBUTING.md](docs/CONTRIBUTING.md) for contribution guidelines before making your first contribution!
+
+## Comparison to other implementations
+
+There are TUF implementations in a variety of programming languages. Some other Go implementations of TUF include:
+
+* [Notary](https://github.com/notaryproject/notary): A version of TUF designed specifically for publishing and managing trusted collections of content. It was used by Docker Content Trust, and has since been superseded by the [Notation](https://github.com/notaryproject/notation) project. In contrast, go-tuf is a direct implementation of TUF and has been updated to conform to 1.0.0 of the TUF specification.
+
diff --git a/client/client.go b/client/client.go
new file mode 100644
index 0000000..978ebf1
--- /dev/null
+++ b/client/client.go
@@ -0,0 +1,989 @@
+package client
+
+import (
+ "bytes"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/internal/roles"
+ "github.com/theupdateframework/go-tuf/util"
+ "github.com/theupdateframework/go-tuf/verify"
+)
+
+const (
+ // This is the upper limit in bytes we will use to limit the download
+ // size of the root/timestamp roles, since we might not don't know how
+ // big it is.
+ defaultRootDownloadLimit = 512000
+ defaultTimestampDownloadLimit = 16384
+ defaultMaxDelegations = 32
+ defaultMaxRootRotations = 1e3
+)
+
+// LocalStore is local storage for downloaded top-level metadata.
+type LocalStore interface {
+ io.Closer
+
+ // GetMeta returns top-level metadata from local storage. The keys are
+ // in the form `ROLE.json`, with ROLE being a valid top-level role.
+ GetMeta() (map[string]json.RawMessage, error)
+
+ // SetMeta persists the given top-level metadata in local storage, the
+ // name taking the same format as the keys returned by GetMeta.
+ SetMeta(name string, meta json.RawMessage) error
+
+ // DeleteMeta deletes a given metadata.
+ DeleteMeta(name string) error
+}
+
+// RemoteStore downloads top-level metadata and target files from a remote
+// repository.
+type RemoteStore interface {
+ // GetMeta downloads the given metadata from remote storage.
+ //
+ // `name` is the filename of the metadata (e.g. "root.json")
+ //
+ // `err` is ErrNotFound if the given file does not exist.
+ //
+ // `size` is the size of the stream, -1 indicating an unknown length.
+ GetMeta(name string) (stream io.ReadCloser, size int64, err error)
+
+ // GetTarget downloads the given target file from remote storage.
+ //
+ // `path` is the path of the file relative to the root of the remote
+ // targets directory (e.g. "/path/to/file.txt").
+ //
+ // `err` is ErrNotFound if the given file does not exist.
+ //
+ // `size` is the size of the stream, -1 indicating an unknown length.
+ GetTarget(path string) (stream io.ReadCloser, size int64, err error)
+}
+
+// Client provides methods for fetching updates from a remote repository and
+// downloading remote target files.
+type Client struct {
+ local LocalStore
+ remote RemoteStore
+
+ // The following four fields represent the versions of metatdata either
+ // from local storage or from recently downloaded metadata
+ rootVer int64
+ targetsVer int64
+ snapshotVer int64
+ timestampVer int64
+
+ // targets is the list of available targets, either from local storage
+ // or from recently downloaded targets metadata
+ targets data.TargetFiles
+
+ // localMeta is the raw metadata from local storage and is used to
+ // check whether remote metadata is present locally
+ localMeta map[string]json.RawMessage
+
+ // db is a key DB used for verifying metadata
+ db *verify.DB
+
+ // consistentSnapshot indicates whether the remote storage is using
+ // consistent snapshots (as specified in root.json)
+ consistentSnapshot bool
+
+ // MaxDelegations limits by default the number of delegations visited for any
+ // target
+ MaxDelegations int
+
+ // MaxRootRotations limits the number of downloaded roots in 1.0.19 root updater
+ MaxRootRotations int
+}
+
+func NewClient(local LocalStore, remote RemoteStore) *Client {
+ return &Client{
+ local: local,
+ remote: remote,
+ MaxDelegations: defaultMaxDelegations,
+ MaxRootRotations: defaultMaxRootRotations,
+ }
+}
+
+// Init 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) Init(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.
+//
+// https://theupdateframework.github.io/specification/v1.0.19/index.html#load-trusted-root
+func (c *Client) Update() (data.TargetFiles, error) {
+ if err := c.UpdateRoots(); err != nil {
+ if _, ok := err.(verify.ErrExpired); ok {
+ // For backward compatibility, we wrap the ErrExpired inside
+ // ErrDecodeFailed.
+ return nil, ErrDecodeFailed{"root.json", err}
+ }
+ return nil, err
+ }
+
+ // Load trusted metadata files, if any, and verify them against the latest root
+ c.getLocalMeta()
+
+ // 5.4.1 - Download the timestamp metadata
+ timestampJSON, err := c.downloadMetaUnsafe("timestamp.json", defaultTimestampDownloadLimit)
+ if err != nil {
+ return nil, err
+ }
+ // 5.4.(2,3 and 4) - Verify timestamp against various attacks
+ // Returns the extracted snapshot metadata
+ snapshotMeta, sameTimestampVersion, err := c.decodeTimestamp(timestampJSON)
+ if sameTimestampVersion {
+ // The new timestamp.json file had the same version; we don't need to
+ // update, so bail early.
+ return c.targets, nil
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ // 5.4.5 - Persist the timestamp metadata
+ if err := c.local.SetMeta("timestamp.json", timestampJSON); err != nil {
+ return nil, err
+ }
+
+ // 5.5.1 - Download snapshot metadata
+ // 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
+ snapshotJSON, err := c.downloadMetaFromTimestamp("snapshot.json", snapshotMeta)
+ if err != nil {
+ return nil, err
+ }
+ // 5.5.(3,5 and 6) - Verify snapshot against various attacks
+ // Returns the extracted metadata files
+ snapshotMetas, err := c.decodeSnapshot(snapshotJSON)
+ if err != nil {
+ return nil, err
+ }
+ // 5.5.7 - Persist snapshot metadata
+ if err := c.local.SetMeta("snapshot.json", snapshotJSON); err != nil {
+ return nil, err
+ }
+
+ // If we don't have the targets.json, download it, determine updated
+ // targets and save targets.json in local storage
+ var updatedTargets data.TargetFiles
+ targetsMeta := snapshotMetas["targets.json"]
+ if !c.hasMetaFromSnapshot("targets.json", targetsMeta) {
+ // 5.6.1 - Download the top-level targets metadata file
+ // 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
+ targetsJSON, err := c.downloadMetaFromSnapshot("targets.json", targetsMeta)
+ if err != nil {
+ return nil, err
+ }
+ // 5.6.(3 and 5) - Verify signatures and check against freeze attack
+ updatedTargets, err = c.decodeTargets(targetsJSON)
+ if err != nil {
+ return nil, err
+ }
+ // 5.6.6 - Persist targets metadata
+ if err := c.local.SetMeta("targets.json", targetsJSON); err != nil {
+ return nil, err
+ }
+ }
+
+ return updatedTargets, nil
+}
+
+func (c *Client) UpdateRoots() error {
+ // https://theupdateframework.github.io/specification/v1.0.19/index.html#load-trusted-root
+ // 5.2 Load the trusted root metadata file. We assume that a good,
+ // trusted copy of this file was shipped with the package manager
+ // or software updater using an out-of-band process.
+ if err := c.loadAndVerifyLocalRootMeta( /*ignoreExpiredCheck=*/ true); err != nil {
+ return err
+ }
+ m, err := c.local.GetMeta()
+ if err != nil {
+ return err
+ }
+
+ type KeyInfo struct {
+ KeyIDs map[string]bool
+ Threshold int
+ }
+
+ // Prepare for 5.3.11: If the timestamp and / or snapshot keys have been rotated,
+ // then delete the trusted timestamp and snapshot metadata files.
+ getKeyInfo := func(role string) KeyInfo {
+ keyIDs := make(map[string]bool)
+ for k := range c.db.GetRole(role).KeyIDs {
+ keyIDs[k] = true
+ }
+ return KeyInfo{keyIDs, c.db.GetRole(role).Threshold}
+ }
+
+ // The nonRootKeyInfo looks like this:
+ // {
+ // "timestamp": {KeyIDs={"KEYID1": true, "KEYID2": true}, Threshold=2},
+ // "snapshot": {KeyIDs={"KEYID3": true}, Threshold=1},
+ // "targets": {KeyIDs={"KEYID4": true, "KEYID5": true, "KEYID6": true}, Threshold=1}
+ // }
+
+ nonRootKeyInfo := map[string]KeyInfo{"timestamp": {}, "snapshot": {}, "targets": {}}
+ for k := range nonRootKeyInfo {
+ nonRootKeyInfo[k] = getKeyInfo(k)
+ }
+
+ // 5.3.1 Temorarily turn on the consistent snapshots in order to download
+ // versioned root metadata files as described next.
+ consistentSnapshot := c.consistentSnapshot
+ c.consistentSnapshot = true
+
+ nRootMetadata := m["root.json"]
+
+ // https://theupdateframework.github.io/specification/v1.0.19/index.html#update-root
+
+ // 5.3.1 Since it may now be signed using entirely different keys,
+ // the client MUST somehow be able to establish a trusted line of
+ // continuity to the latest set of keys (see § 6.1 Key
+ // management and migration). To do so, the client MUST
+ // download intermediate root metadata files, until the
+ // latest available one is reached. Therefore, it MUST
+ // temporarily turn on consistent snapshots in order to
+ // download versioned root metadata files as described next.
+
+ // This loop returns on error or breaks after downloading the lastest root metadata.
+ // 5.3.2 Let N denote the version number of the trusted root metadata file.
+ for i := 0; i < c.MaxRootRotations; i++ {
+ // 5.3.3 Try downloading version nPlusOne of the root metadata file.
+ // NOTE: as a side effect, we do update c.rootVer to nPlusOne between iterations.
+ nPlusOne := c.rootVer + 1
+ nPlusOneRootPath := util.VersionedPath("root.json", nPlusOne)
+ nPlusOneRootMetadata, err := c.downloadMetaUnsafe(nPlusOneRootPath, defaultRootDownloadLimit)
+
+ if err != nil {
+ if _, ok := err.(ErrMissingRemoteMetadata); ok {
+ // stop when the next root can't be downloaded
+ break
+ }
+ return err
+ }
+
+ // 5.3.4 Check for an arbitrary software attack.
+ // 5.3.4.1 Check that N signed N+1
+ nPlusOneRootMetadataSigned, err := c.verifyRoot(nRootMetadata, nPlusOneRootMetadata)
+ if err != nil {
+ return err
+ }
+
+ // 5.3.4.2 check that N+1 signed itself.
+ if _, err := c.verifyRoot(nPlusOneRootMetadata, nPlusOneRootMetadata); err != nil {
+ // 5.3.6 Note that the expiration of the new (intermediate) root
+ // metadata file does not matter yet, because we will check for
+ // it in step 5.3.10.
+ return err
+ }
+
+ // 5.3.5 Check for a rollback attack. Here, we check that nPlusOneRootMetadataSigned.version == nPlusOne.
+ if nPlusOneRootMetadataSigned.Version != nPlusOne {
+ return verify.ErrWrongVersion{
+ Given: nPlusOneRootMetadataSigned.Version,
+ Expected: nPlusOne,
+ }
+ }
+
+ // 5.3.7 Set the trusted root metadata file to the new root metadata file.
+ c.rootVer = nPlusOneRootMetadataSigned.Version
+ // NOTE: following up on 5.3.1, we want to always have consistent snapshots on for the duration
+ // of root rotation. AFTER the rotation is over, we will set it to the value of the last root.
+ consistentSnapshot = nPlusOneRootMetadataSigned.ConsistentSnapshot
+ // 5.3.8 Persist root metadata. The client MUST write the file to non-volatile storage as FILENAME.EXT (e.g. root.json).
+ // NOTE: Internally, setMeta stores metadata in LevelDB in a persistent manner.
+ if err := c.local.SetMeta("root.json", nPlusOneRootMetadata); err != nil {
+ return err
+ }
+ nRootMetadata = nPlusOneRootMetadata
+ // 5.3.9 Repeat steps 5.3.2 to 5.3.9
+
+ } // End of the for loop.
+
+ // 5.3.10 Check for a freeze attack.
+ // NOTE: This will check for any, including freeze, attack.
+ if err := c.loadAndVerifyLocalRootMeta( /*ignoreExpiredCheck=*/ false); err != nil {
+ return err
+ }
+
+ countDeleted := func(s1 map[string]bool, s2 map[string]bool) int {
+ c := 0
+ for k := range s1 {
+ if _, ok := s2[k]; !ok {
+ c++
+ }
+ }
+ return c
+ }
+
+ // 5.3.11 To recover from fast-forward attack, certain metadata files need
+ // to be deleted if a threshold of keys are revoked.
+ // List of metadata that should be deleted per role if a threshold of keys
+ // are revoked:
+ // (based on the ongoing PR: https://github.com/mnm678/specification/tree/e50151d9df632299ddea364c4f44fe8ca9c10184)
+ // timestamp -> delete timestamp.json
+ // snapshot -> delete timestamp.json and snapshot.json
+ // targets -> delete snapshot.json and targets.json
+ //
+ // nonRootKeyInfo contains the keys and thresholds from root.json
+ // that were on disk before the root update process begins.
+ for topLevelRolename := range nonRootKeyInfo {
+ // ki contains the keys and thresholds from the latest downloaded root.json.
+ ki := getKeyInfo(topLevelRolename)
+ if countDeleted(nonRootKeyInfo[topLevelRolename].KeyIDs, ki.KeyIDs) >= nonRootKeyInfo[topLevelRolename].Threshold {
+ deleteMeta := map[string][]string{
+ "timestamp": {"timestamp.json"},
+ "snapshot": {"timestamp.json", "snapshot.json"},
+ "targets": {"snapshot.json", "targets.json"},
+ }
+
+ for _, r := range deleteMeta[topLevelRolename] {
+ c.local.DeleteMeta(r)
+ }
+ }
+ }
+
+ // 5.3.12 Set whether consistent snapshots are used as per the trusted root metadata file.
+ c.consistentSnapshot = consistentSnapshot
+ return nil
+}
+
+// getLocalMeta decodes and verifies metadata from local storage.
+// The verification of local files is purely for consistency, if an attacker
+// has compromised the local storage, there is no guarantee it can be trusted.
+// Before trying to load the metadata files, it clears the in-memory copy of the local metadata.
+// This is to insure that all of the loaded metadata files at the end are indeed verified by the latest root.
+// If some of the metadata files fail to load it will proceed with trying to load the rest,
+// but still return an error at the end, if such occurred. Otherwise returns nil.
+func (c *Client) getLocalMeta() error {
+ var retErr error
+ loadFailed := false
+ // Clear the in-memory copy of the local metadata. The goal is to reload and take into account
+ // only the metadata files that are verified by the latest root. Otherwise, their content should
+ // be ignored.
+ c.localMeta = make(map[string]json.RawMessage)
+
+ // Load the latest root meta
+ if err := c.loadAndVerifyLocalRootMeta( /*ignoreExpiredCheck=*/ false); err != nil {
+ return err
+ }
+
+ // Load into memory the existing meta, if any, from the local storage
+ meta, err := c.local.GetMeta()
+ if err != nil {
+ return nil
+ }
+
+ // Verify the top-level metadata (timestamp, snapshot and targets) against the latest root and load it, if okay
+ if timestampJSON, ok := meta["timestamp.json"]; ok {
+ timestamp := &data.Timestamp{}
+ if err := c.db.UnmarshalTrusted(timestampJSON, timestamp, "timestamp"); err != nil {
+ loadFailed = true
+ retErr = err
+ } else {
+ c.localMeta["timestamp.json"] = meta["timestamp.json"]
+ c.timestampVer = timestamp.Version
+ }
+ }
+
+ snapshot := &data.Snapshot{}
+ if snapshotJSON, ok := meta["snapshot.json"]; ok {
+ if err := c.db.UnmarshalTrusted(snapshotJSON, snapshot, "snapshot"); err != nil {
+ loadFailed = true
+ retErr = err
+ } else {
+ c.localMeta["snapshot.json"] = meta["snapshot.json"]
+ c.snapshotVer = snapshot.Version
+ }
+ }
+
+ if targetsJSON, ok := meta["targets.json"]; ok {
+ targets := &data.Targets{}
+ if err := c.db.UnmarshalTrusted(targetsJSON, targets, "targets"); err != nil {
+ loadFailed = true
+ retErr = err
+ } else {
+ c.localMeta["targets.json"] = meta["targets.json"]
+ c.targetsVer = targets.Version
+ // FIXME(TUF-0.9) temporarily support files with leading path separators.
+ // c.targets = targets.Targets
+ c.loadTargets(targets.Targets)
+ }
+ }
+
+ if loadFailed {
+ // If any of the metadata failed to be verified, return the reason for that failure
+ // and fail fast before delegated targets
+ return retErr
+ }
+
+ // verifiedDelegatedTargets is a set of verified delegated targets
+ verifiedDelegatedTargets := make(map[string]bool)
+ for fileName := range meta {
+ if !verifiedDelegatedTargets[fileName] && roles.IsDelegatedTargetsManifest(fileName) {
+ if delegationPath, err := c.getDelegationPathFromRaw(snapshot, meta[fileName]); err != nil {
+ loadFailed = true
+ retErr = err
+ } else {
+ // Every delegated targets in the path has been verified
+ // as a side effect of getDelegationPathFromRaw
+ for _, key := range delegationPath {
+ fileName := fmt.Sprintf("%s.json", key)
+ verifiedDelegatedTargets[fileName] = true
+ }
+ }
+ }
+ }
+
+ for fileName := range verifiedDelegatedTargets {
+ c.localMeta[fileName] = meta[fileName]
+ }
+
+ if loadFailed {
+ // If any of the metadata failed to be verified, return the reason for that failure
+ return retErr
+ }
+ return nil
+}
+
+// getDelegationPathFromRaw verifies a delegated targets against
+// a given snapshot and returns an error if it's invalid
+//
+// Delegation must have targets to get a path, else an empty list
+// will be returned: this is because the delegation iterator is leveraged.
+//
+// Concrete example:
+// targets
+// └── a.json
+//   └── b.json
+//      └── c.json
+//        └── target_file.txt
+//
+// If you try to use that function on "a.json" or "b.json", it'll return an empty list
+// with no error, as neither of them declare a target file
+// On the other hand, if you use that function on "c.json", it'll return & verify
+// [c.json, b.json, a.json]. Running that function on every delegated targets
+// guarantees that if a delegated targets is in the path of a target file, then it will
+// appear at least once in the result
+func (c *Client) getDelegationPathFromRaw(snapshot *data.Snapshot, delegatedTargetsJSON json.RawMessage) ([]string, error) {
+ // unmarshal the delegated targets first without verifying as
+ // we need at least one targets file name to leverage the
+ // getTargetFileMetaDelegationPath method
+ s := &data.Signed{}
+ if err := json.Unmarshal(delegatedTargetsJSON, s); err != nil {
+ return nil, err
+ }
+ targets := &data.Targets{}
+ if err := json.Unmarshal(s.Signed, targets); err != nil {
+ return nil, err
+ }
+ for targetPath := range targets.Targets {
+ _, resp, err := c.getTargetFileMetaDelegationPath(targetPath, snapshot)
+ // We only need to test one targets file:
+ // - If it is valid, it means the delegated targets has been validated
+ // - If it is not, the delegated targets isn't valid
+ return resp, err
+ }
+ return nil, nil
+}
+
+// loadAndVerifyLocalRootMeta decodes and verifies root metadata from
+// local storage 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) loadAndVerifyLocalRootMeta(ignoreExpiredCheck bool) error {
+ meta, err := c.local.GetMeta()
+ if err != nil {
+ return err
+ }
+ rootJSON, ok := meta["root.json"]
+ 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{}
+ if err := json.Unmarshal(rootJSON, s); err != nil {
+ return err
+ }
+ root := &data.Root{}
+ if err := json.Unmarshal(s.Signed, root); err != nil {
+ return err
+ }
+ ndb := verify.NewDB()
+ for id, k := range root.Keys {
+ if err := ndb.AddKey(id, k); err != nil {
+ return err
+ }
+ }
+ for name, role := range root.Roles {
+ if err := ndb.AddRole(name, role); err != nil {
+ return err
+ }
+ }
+ // Any trusted local root metadata version must be greater than 0.
+ if ignoreExpiredCheck {
+ if err := ndb.VerifyIgnoreExpiredCheck(s, "root", 0); err != nil {
+ return err
+ }
+ } else {
+ if err := ndb.Verify(s, "root", 0); err != nil {
+ return err
+ }
+ }
+ c.consistentSnapshot = root.ConsistentSnapshot
+ c.rootVer = root.Version
+ c.db = ndb
+ return nil
+}
+
+// verifyRoot verifies Signed section of the bJSON
+// using verification keys in aJSON.
+func (c *Client) verifyRoot(aJSON []byte, bJSON []byte) (*data.Root, error) {
+ aSigned := &data.Signed{}
+ if err := json.Unmarshal(aJSON, aSigned); err != nil {
+ return nil, err
+ }
+ aRoot := &data.Root{}
+ if err := json.Unmarshal(aSigned.Signed, aRoot); err != nil {
+ return nil, err
+ }
+
+ bSigned := &data.Signed{}
+ if err := json.Unmarshal(bJSON, bSigned); err != nil {
+ return nil, err
+ }
+ bRoot := &data.Root{}
+ if err := json.Unmarshal(bSigned.Signed, bRoot); err != nil {
+ return nil, err
+ }
+
+ ndb := verify.NewDB()
+ for id, k := range aRoot.Keys {
+ if err := ndb.AddKey(id, k); err != nil {
+ return nil, err
+ }
+ }
+ for name, role := range aRoot.Roles {
+ if err := ndb.AddRole(name, role); err != nil {
+ return nil, err
+ }
+ }
+
+ if err := ndb.VerifySignatures(bSigned, "root"); err != nil {
+ return nil, err
+ }
+ return bRoot, nil
+}
+
+// FIXME(TUF-0.9) TUF is considering removing support for target files starting
+// with a leading path separator. In order to be backwards compatible, we'll
+// just remove leading separators for now.
+func (c *Client) loadTargets(targets data.TargetFiles) {
+ c.targets = make(data.TargetFiles)
+ for name, meta := range targets {
+ c.targets[name] = meta
+ c.targets[util.NormalizeTarget(name)] = meta
+ }
+}
+
+// downloadMetaUnsafe downloads top-level metadata from remote storage without
+// verifying it's length and hashes (used for example to download timestamp.json
+// which has unknown size). It will download at most maxMetaSize bytes.
+func (c *Client) downloadMetaUnsafe(name string, maxMetaSize int64) ([]byte, error) {
+ r, size, err := c.remote.GetMeta(name)
+ if err != nil {
+ if IsNotFound(err) {
+ return nil, ErrMissingRemoteMetadata{name}
+ }
+ return nil, ErrDownloadFailed{name, err}
+ }
+ defer r.Close()
+
+ // return ErrMetaTooLarge if the reported size is greater than maxMetaSize
+ if size > maxMetaSize {
+ return nil, ErrMetaTooLarge{name, size, maxMetaSize}
+ }
+
+ // although the size has been checked above, use a LimitReader in case
+ // the reported size is inaccurate, or size is -1 which indicates an
+ // unknown length
+ return io.ReadAll(io.LimitReader(r, maxMetaSize))
+}
+
+// remoteGetFunc is the type of function the download method uses to download
+// remote files
+type remoteGetFunc func(string) (io.ReadCloser, int64, error)
+
+// downloadHashed tries to download the hashed prefixed version of the file.
+func (c *Client) downloadHashed(file string, get remoteGetFunc, hashes data.Hashes) (io.ReadCloser, int64, error) {
+ // try each hashed path in turn, and either return the contents,
+ // try the next one if a 404 is returned, or return an error
+ for _, path := range util.HashedPaths(file, hashes) {
+ r, size, err := get(path)
+ if err != nil {
+ if IsNotFound(err) {
+ continue
+ }
+ return nil, 0, err
+ }
+ return r, size, nil
+ }
+ return nil, 0, ErrNotFound{file}
+}
+
+// download downloads the given target file from remote storage using the get
+// function, adding hashes to the path if consistent snapshots are in use
+func (c *Client) downloadTarget(file string, get remoteGetFunc, hashes data.Hashes) (io.ReadCloser, int64, error) {
+ if c.consistentSnapshot {
+ return c.downloadHashed(file, get, hashes)
+ } else {
+ return get(file)
+ }
+}
+
+// downloadVersionedMeta downloads top-level metadata from remote storage and
+// verifies it using the given file metadata.
+func (c *Client) downloadMeta(name string, version int64, m data.FileMeta) ([]byte, error) {
+ r, size, err := func() (io.ReadCloser, int64, error) {
+ if c.consistentSnapshot {
+ path := util.VersionedPath(name, version)
+ r, size, err := c.remote.GetMeta(path)
+ if err == nil {
+ return r, size, nil
+ }
+
+ return nil, 0, err
+ } else {
+ return c.remote.GetMeta(name)
+ }
+ }()
+ if err != nil {
+ if IsNotFound(err) {
+ return nil, ErrMissingRemoteMetadata{name}
+ }
+ return nil, err
+ }
+ defer r.Close()
+
+ // return ErrWrongSize if the reported size is known and incorrect
+ var stream io.Reader
+ if m.Length != 0 {
+ if size >= 0 && size != m.Length {
+ return nil, ErrWrongSize{name, size, m.Length}
+ }
+
+ // wrap the data in a LimitReader so we download at most m.Length bytes
+ stream = io.LimitReader(r, m.Length)
+ } else {
+ stream = r
+ }
+
+ return io.ReadAll(stream)
+}
+
+func (c *Client) downloadMetaFromSnapshot(name string, m data.SnapshotFileMeta) ([]byte, error) {
+ b, err := c.downloadMeta(name, m.Version, data.FileMeta{Length: m.Length, Hashes: m.Hashes})
+ if err != nil {
+ return nil, err
+ }
+
+ // 5.6.2 – Check length and hashes of fetched bytes *before* parsing metadata
+ if err := util.BytesMatchLenAndHashes(b, m.Length, m.Hashes); err != nil {
+ return nil, ErrDownloadFailed{name, err}
+ }
+
+ meta, err := util.GenerateSnapshotFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
+ if err != nil {
+ return nil, err
+ }
+
+ // 5.6.4 - Check against snapshot role's version
+ if err := util.VersionEqual(meta.Version, m.Version); err != nil {
+ return nil, ErrDownloadFailed{name, err}
+ }
+
+ return b, nil
+}
+
+func (c *Client) downloadMetaFromTimestamp(name string, m data.TimestampFileMeta) ([]byte, error) {
+ b, err := c.downloadMeta(name, m.Version, data.FileMeta{Length: m.Length, Hashes: m.Hashes})
+ if err != nil {
+ return nil, err
+ }
+
+ // 5.2.2. – Check length and hashes of fetched bytes *before* parsing metadata
+ if err := util.BytesMatchLenAndHashes(b, m.Length, m.Hashes); err != nil {
+ return nil, ErrDownloadFailed{name, err}
+ }
+
+ meta, err := util.GenerateTimestampFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
+ if err != nil {
+ return nil, err
+ }
+
+ // 5.5.4 - Check against timestamp role's version
+ if err := util.VersionEqual(meta.Version, m.Version); err != nil {
+ return nil, ErrDownloadFailed{name, err}
+ }
+
+ return b, nil
+}
+
+// decodeSnapshot decodes and verifies snapshot metadata, and returns the new
+// root and targets file meta.
+func (c *Client) decodeSnapshot(b json.RawMessage) (data.SnapshotFiles, error) {
+ snapshot := &data.Snapshot{}
+ // 5.5.(3 and 6) - Verify it's signed correctly and it's not expired
+ if err := c.db.Unmarshal(b, snapshot, "snapshot", c.snapshotVer); err != nil {
+ return data.SnapshotFiles{}, ErrDecodeFailed{"snapshot.json", err}
+ }
+ // 5.5.5 - Check for top-level targets rollback attack
+ // Verify explicitly that current targets meta version is less than or equal to the new one
+ if snapshot.Meta["targets.json"].Version < c.targetsVer {
+ return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: snapshot.Meta["targets.json"].Version, Current: c.targetsVer}
+ }
+
+ // 5.5.5 - Get the local/trusted snapshot metadata, if any, and check all target metafiles against rollback attack
+ // In case the local snapshot metadata was not verified by the keys in the latest root during getLocalMeta(),
+ // snapshot.json won't be present in c.localMeta and thus this check will not be processed.
+ if snapshotJSON, ok := c.localMeta["snapshot.json"]; ok {
+ currentSnapshot := &data.Snapshot{}
+ if err := c.db.UnmarshalTrusted(snapshotJSON, currentSnapshot, "snapshot"); err != nil {
+ return data.SnapshotFiles{}, err
+ }
+ // 5.5.5 - Check for rollback attacks in both top-level and delegated targets roles (note that the Meta object includes both)
+ for path, local := range currentSnapshot.Meta {
+ if newMeta, ok := snapshot.Meta[path]; ok {
+ // 5.5.5 - Check for rollback attack
+ if newMeta.Version < local.Version {
+ return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: newMeta.Version, Current: local.Version}
+ }
+ } else {
+ // 5.5.5 - Abort the update if a target file has been removed from the new snapshot file
+ return data.SnapshotFiles{}, verify.ErrMissingTargetFile
+ }
+ }
+ }
+ // At this point we can trust the new snapshot, the top-level targets, and any delegated targets versions it refers to
+ // so we can update the client's trusted versions and proceed with persisting the new snapshot metadata
+ // c.snapshotVer was already set when we verified the timestamp metadata
+ c.targetsVer = snapshot.Meta["targets.json"].Version
+ return snapshot.Meta, nil
+}
+
+// decodeTargets decodes and verifies targets metadata, sets c.targets and
+// returns updated targets.
+func (c *Client) decodeTargets(b json.RawMessage) (data.TargetFiles, error) {
+ targets := &data.Targets{}
+ // 5.6.(3 and 5) - Verify signatures and check against freeze attack
+ if err := c.db.Unmarshal(b, targets, "targets", c.targetsVer); err != nil {
+ return nil, ErrDecodeFailed{"targets.json", err}
+ }
+ // Generate a list with the updated targets
+ updatedTargets := make(data.TargetFiles)
+ for path, meta := range targets.Targets {
+ if local, ok := c.targets[path]; ok {
+ if err := util.TargetFileMetaEqual(local, meta); err == nil {
+ continue
+ }
+ }
+ updatedTargets[path] = meta
+ }
+ // c.targetsVer was already updated when we verified the snapshot metadata
+ // FIXME(TUF-0.9) temporarily support files with leading path separators.
+ // c.targets = targets.Targets
+ c.loadTargets(targets.Targets)
+ return updatedTargets, nil
+}
+
+// decodeTimestamp decodes and verifies timestamp metadata, and returns the
+// new snapshot file meta and signals whether the update should be aborted early
+// (the new timestamp has the same version as the old one, so there's no need to
+// complete the update).
+func (c *Client) decodeTimestamp(b json.RawMessage) (data.TimestampFileMeta, bool, error) {
+ timestamp := &data.Timestamp{}
+
+ if err := c.db.Unmarshal(b, timestamp, "timestamp", c.timestampVer); err != nil {
+ return data.TimestampFileMeta{}, false, ErrDecodeFailed{"timestamp.json", err}
+ }
+ // 5.4.3.1 - Check for timestamp rollback attack
+ // We already checked for timestamp.Version < c.timestampVer in the Unmarshal call above.
+ // Here, we're checking for version equality, which indicates that we can abandon this update.
+ if timestamp.Version == c.timestampVer {
+ return data.TimestampFileMeta{}, true, nil
+ }
+ // 5.4.3.2 - Check for snapshot rollback attack
+ // Verify that the current snapshot meta version is less than or equal to the new one
+ if timestamp.Meta["snapshot.json"].Version < c.snapshotVer {
+ return data.TimestampFileMeta{}, false, verify.ErrLowVersion{Actual: timestamp.Meta["snapshot.json"].Version, Current: c.snapshotVer}
+ }
+ // At this point we can trust the new timestamp and the snapshot version it refers to
+ // so we can update the client's trusted versions and proceed with persisting the new timestamp
+ c.timestampVer = timestamp.Version
+ c.snapshotVer = timestamp.Meta["snapshot.json"].Version
+ return timestamp.Meta["snapshot.json"], false, nil
+}
+
+// hasMetaFromSnapshot checks whether local metadata has the given meta
+func (c *Client) hasMetaFromSnapshot(name string, m data.SnapshotFileMeta) bool {
+ _, ok := c.localMetaFromSnapshot(name, m)
+ return ok
+}
+
+// localMetaFromSnapshot returns localmetadata if it matches the snapshot
+func (c *Client) localMetaFromSnapshot(name string, m data.SnapshotFileMeta) (json.RawMessage, bool) {
+ b, ok := c.localMeta[name]
+ if !ok {
+ return nil, false
+ }
+ meta, err := util.GenerateSnapshotFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
+ if err != nil {
+ return nil, false
+ }
+ err = util.SnapshotFileMetaEqual(meta, m)
+ return b, err == nil
+}
+
+type Destination interface {
+ io.Writer
+ Delete() error
+}
+
+// Download downloads the given target file from remote storage into dest.
+//
+// dest will be deleted and an error returned in the following situations:
+//
+// - The target does not exist in the local targets.json
+// - Failed to fetch the chain of delegations accessible from local snapshot.json
+// - The target does not exist in any targets
+// - Metadata cannot be generated for the downloaded data
+// - Generated metadata does not match local metadata for the given file
+// - Size of the download does not match if the reported size is known and
+// incorrect
+func (c *Client) Download(name string, dest Destination) (err error) {
+ // delete dest if there is an error
+ defer func() {
+ if err != nil {
+ dest.Delete()
+ }
+ }()
+
+ // populate c.targets from local storage if not set
+ if c.targets == nil {
+ if err := c.getLocalMeta(); err != nil {
+ return err
+ }
+ }
+
+ normalizedName := util.NormalizeTarget(name)
+ localMeta, ok := c.targets[normalizedName]
+ if !ok {
+ // search in delegations
+ localMeta, err = c.getTargetFileMeta(normalizedName)
+ if err != nil {
+ return err
+ }
+ }
+
+ // get the data from remote storage
+ r, size, err := c.downloadTarget(normalizedName, c.remote.GetTarget, localMeta.Hashes)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ // return ErrWrongSize if the reported size is known and incorrect
+ if size >= 0 && size != localMeta.Length {
+ return ErrWrongSize{name, size, localMeta.Length}
+ }
+
+ // wrap the data in a LimitReader so we download at most localMeta.Length bytes
+ stream := io.LimitReader(r, localMeta.Length)
+
+ // read the data, simultaneously writing it to dest and generating metadata
+ actual, err := util.GenerateTargetFileMeta(io.TeeReader(stream, dest), localMeta.HashAlgorithms()...)
+ if err != nil {
+ return ErrDownloadFailed{name, err}
+ }
+
+ // check the data has the correct length and hashes
+ if err := util.TargetFileMetaEqual(actual, localMeta); err != nil {
+ if e, ok := err.(util.ErrWrongLength); ok {
+ return ErrWrongSize{name, e.Actual, e.Expected}
+ }
+ return ErrDownloadFailed{name, err}
+ }
+
+ return nil
+}
+
+func (c *Client) VerifyDigest(digest string, digestAlg string, length int64, path string) error {
+ localMeta, ok := c.targets[path]
+ if !ok {
+ return ErrUnknownTarget{Name: path, SnapshotVersion: c.snapshotVer}
+ }
+
+ actual := data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)}
+ var err error
+ actual.Hashes[digestAlg], err = hex.DecodeString(digest)
+ if err != nil {
+ return err
+ }
+
+ if err := util.TargetFileMetaEqual(data.TargetFileMeta{FileMeta: actual}, localMeta); err != nil {
+ if e, ok := err.(util.ErrWrongLength); ok {
+ return ErrWrongSize{path, e.Actual, e.Expected}
+ }
+ return ErrDownloadFailed{path, err}
+ }
+
+ return nil
+}
+
+// Target returns the target metadata for a specific target if it
+// exists, searching from top-level level targets then through
+// all delegations. If it does not, ErrNotFound will be returned.
+func (c *Client) Target(name string) (data.TargetFileMeta, error) {
+ target, err := c.getTargetFileMeta(util.NormalizeTarget(name))
+ if err == nil {
+ return target, nil
+ }
+
+ if _, ok := err.(ErrUnknownTarget); ok {
+ return data.TargetFileMeta{}, ErrNotFound{name}
+ }
+
+ return data.TargetFileMeta{}, err
+}
+
+// Targets returns the complete list of available top-level targets.
+func (c *Client) Targets() (data.TargetFiles, error) {
+ // populate c.targets from local storage if not set
+ if c.targets == nil {
+ if err := c.getLocalMeta(); err != nil {
+ return nil, err
+ }
+ }
+ return c.targets, nil
+}
diff --git a/client/client_test.go b/client/client_test.go
new file mode 100644
index 0000000..f4dbd88
--- /dev/null
+++ b/client/client_test.go
@@ -0,0 +1,1668 @@
+package client
+
+import (
+ "bytes"
+ "crypto/ed25519"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+ "github.com/stretchr/testify/assert"
+ tuf "github.com/theupdateframework/go-tuf"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/internal/sets"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+ "github.com/theupdateframework/go-tuf/sign"
+ "github.com/theupdateframework/go-tuf/util"
+ "github.com/theupdateframework/go-tuf/verify"
+ . "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type ClientSuite struct {
+ store tuf.LocalStore
+ repo *tuf.Repo
+ local LocalStore
+ remote RemoteStore
+ expiredTime time.Time
+ keyIDs map[string][]string
+ useFileStore bool
+ // Only used with FileStore
+ tmpDir string
+}
+
+var _ = Suite(&ClientSuite{useFileStore: false})
+var _ = Suite(&ClientSuite{useFileStore: true})
+
+func newFakeRemoteStore() *fakeRemoteStore {
+ return &fakeRemoteStore{
+ meta: make(map[string]*fakeFile),
+ targets: make(map[string]*fakeFile),
+ }
+}
+
+type fakeRemoteStore struct {
+ meta map[string]*fakeFile
+ targets map[string]*fakeFile
+}
+
+func (f *fakeRemoteStore) GetMeta(name string) (io.ReadCloser, int64, error) {
+ return f.get(name, f.meta)
+}
+
+func (f *fakeRemoteStore) GetTarget(path string) (io.ReadCloser, int64, error) {
+ return f.get(path, f.targets)
+}
+
+func (f *fakeRemoteStore) get(name string, store map[string]*fakeFile) (io.ReadCloser, int64, error) {
+ file, ok := store[name]
+ if !ok {
+ return nil, 0, ErrNotFound{name}
+ }
+ return file, file.size, nil
+}
+
+// These are helper methods for manipulating the internals of the Stores
+// because the set/delete methods are not part of the Interface, we need to
+// switch on the underlying implementation.
+// Also readMeta method is convenience for ease of testing.
+func (s *ClientSuite) setRemoteMeta(path string, data []byte) error {
+ switch impl := s.remote.(type) {
+ case *fakeRemoteStore:
+ impl.meta[path] = newFakeFile(data)
+ return nil
+ case *FileRemoteStore:
+ return impl.addMeta(path, data)
+ default:
+ return fmt.Errorf("non-supoprted RemoteStore, got %+v", impl)
+ }
+}
+
+func (s *ClientSuite) setRemoteTarget(path string, data []byte) error {
+ switch impl := s.remote.(type) {
+ case *fakeRemoteStore:
+ impl.targets[path] = newFakeFile(data)
+ return nil
+ case *FileRemoteStore:
+ return impl.addTarget(path, data)
+ default:
+ return fmt.Errorf("non-supoprted RemoteStore, got %+v", impl)
+ }
+}
+
+func (s *ClientSuite) deleteMeta(path string) error {
+ switch impl := s.remote.(type) {
+ case *fakeRemoteStore:
+ delete(impl.meta, path)
+ return nil
+ case *FileRemoteStore:
+ return impl.deleteMeta(path)
+ default:
+ return fmt.Errorf("non-supported RemoteStore, got %+v", impl)
+ }
+}
+
+func (s *ClientSuite) deleteTarget(path string) error {
+ switch impl := s.remote.(type) {
+ case *fakeRemoteStore:
+ delete(impl.targets, path)
+ return nil
+ case *FileRemoteStore:
+ return impl.deleteTarget(path)
+ default:
+ return fmt.Errorf("non-supported RemoteStore, got %+v", impl)
+ }
+}
+
+func (s *ClientSuite) readMeta(name string) ([]byte, error) {
+ stream, _, err := s.remote.GetMeta(name)
+ if err != nil {
+ return nil, err
+ }
+ return io.ReadAll(stream)
+}
+
+func newFakeFile(b []byte) *fakeFile {
+ return &fakeFile{buf: bytes.NewReader(b), size: int64(len(b))}
+}
+
+type fakeFile struct {
+ buf *bytes.Reader
+ bytesRead int
+ size int64
+}
+
+func (f *fakeFile) Read(p []byte) (int, error) {
+ n, err := f.buf.Read(p)
+ f.bytesRead += n
+ return n, err
+}
+
+func (f *fakeFile) Close() error {
+ f.buf.Seek(0, io.SeekStart)
+ return nil
+}
+
+var targetFiles = map[string][]byte{
+ "foo.txt": []byte("foo"),
+ "bar.txt": []byte("bar"),
+ "baz.txt": []byte("baz"),
+}
+
+func (s *ClientSuite) SetUpTest(c *C) {
+ s.store = tuf.MemoryStore(nil, targetFiles)
+
+ // create a valid repo containing foo.txt
+ var err error
+ s.repo, err = tuf.NewRepo(s.store)
+ c.Assert(err, IsNil)
+ // don't use consistent snapshots to make testing easier (consistent
+ // snapshots are tested explicitly elsewhere)
+ c.Assert(s.repo.Init(false), IsNil)
+ s.keyIDs = map[string][]string{
+ "root": s.genKey(c, "root"),
+ "targets": s.genKey(c, "targets"),
+ "snapshot": s.genKey(c, "snapshot"),
+ "timestamp": s.genKey(c, "timestamp"),
+ }
+ c.Assert(s.repo.AddTarget("foo.txt", nil), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+
+ // create a remote store containing valid repo files
+ if s.useFileStore {
+ s.remote, s.tmpDir, err = newTestFileStoreFS()
+ if err != nil {
+ c.Fatalf("failed to create new FileStore: %v", err)
+ }
+ } else {
+ s.remote = newFakeRemoteStore()
+ }
+ s.syncRemote(c)
+ for path, data := range targetFiles {
+ s.setRemoteTarget(path, data)
+ }
+
+ s.expiredTime = time.Now().Add(time.Hour)
+}
+
+func (s *ClientSuite) TearDownTest(c *C) {
+ if s.tmpDir != "" {
+ rmrf(s.tmpDir, c.Logf)
+ }
+}
+
+func (s *ClientSuite) genKey(c *C, role string) []string {
+ ids, err := s.repo.GenKey(role)
+ c.Assert(err, IsNil)
+ return ids
+}
+
+func (s *ClientSuite) genKeyExpired(c *C, role string) []string {
+ ids, err := s.repo.GenKeyWithExpires(role, s.expiredTime)
+ c.Assert(err, IsNil)
+ return ids
+}
+
+// withMetaExpired sets signed.IsExpired throughout the invocation of f so that
+// any metadata marked to expire at s.expiredTime will be expired (this avoids
+// the need to sleep in the tests).
+func (s *ClientSuite) withMetaExpired(f func()) {
+ e := verify.IsExpired
+ defer func() { verify.IsExpired = e }()
+ verify.IsExpired = func(t time.Time) bool {
+ return t.Unix() == s.expiredTime.Round(time.Second).Unix()
+ }
+ f()
+}
+
+func (s *ClientSuite) syncLocal(c *C) {
+ meta, err := s.store.GetMeta()
+ c.Assert(err, IsNil)
+ for k, v := range meta {
+ c.Assert(s.local.SetMeta(k, v), IsNil)
+ }
+}
+
+func (s *ClientSuite) syncRemote(c *C) {
+ meta, err := s.store.GetMeta()
+ c.Assert(err, IsNil)
+ for name, data := range meta {
+ if err := s.setRemoteMeta(name, data); err != nil {
+ panic(fmt.Sprintf("setMetadata failed: %v", err))
+ }
+ }
+}
+
+func (s *ClientSuite) addRemoteTarget(c *C, name string) {
+ c.Assert(s.repo.AddTarget(name, nil), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+}
+
+func (s *ClientSuite) rootMeta(c *C) []byte {
+ meta, err := s.repo.GetMeta()
+ c.Assert(err, IsNil)
+ rootMeta, ok := meta["root.json"]
+ c.Assert(ok, Equals, true)
+ return rootMeta
+}
+
+func (s *ClientSuite) newClient(c *C) *Client {
+ s.local = MemoryLocalStore()
+ client := NewClient(s.local, s.remote)
+ c.Assert(client.Init(s.rootMeta(c)), IsNil)
+ return client
+}
+
+func (s *ClientSuite) updatedClient(c *C) *Client {
+ client := s.newClient(c)
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ return client
+}
+
+func assertFile(c *C, file data.TargetFileMeta, name string) {
+ target, ok := targetFiles[name]
+ if !ok {
+ c.Fatalf("unknown target %s", name)
+ }
+
+ meta, err := util.GenerateTargetFileMeta(bytes.NewReader(target), file.HashAlgorithms()...)
+ c.Assert(err, IsNil)
+ c.Assert(util.TargetFileMetaEqual(file, meta), IsNil)
+}
+
+func assertFiles(c *C, files data.TargetFiles, names []string) {
+ c.Assert(files, HasLen, len(names))
+ for _, name := range names {
+ file, ok := files[name]
+ if !ok {
+ c.Fatalf("expected files to contain %s", name)
+ }
+
+ assertFile(c, file, name)
+ }
+}
+
+func assertWrongHash(c *C, err error) {
+ // just test the type of err rather using DeepEquals as it contains
+ // hashes we don't necessarily need to check.
+ e, ok := err.(ErrDownloadFailed)
+ if !ok {
+ c.Fatalf("expected err to have type ErrDownloadFailed, got %T", err)
+ }
+ if _, ok := e.Err.(util.ErrWrongHash); !ok {
+ c.Fatalf("expected err.Err to have type util.ErrWrongHash, got %T", err)
+ }
+}
+
+func (s *ClientSuite) assertErrExpired(c *C, err error, file string) {
+ decodeErr, ok := err.(ErrDecodeFailed)
+ if !ok {
+ c.Fatalf("expected err to have type ErrDecodeFailed, got %T", err)
+ }
+ c.Assert(decodeErr.File, Equals, file)
+ expiredErr, ok := decodeErr.Err.(verify.ErrExpired)
+ if !ok {
+ c.Fatalf("expected err.Err to have type signed.ErrExpired, got %T", err)
+ }
+ c.Assert(expiredErr.Expired.Unix(), Equals, s.expiredTime.Round(time.Second).Unix())
+}
+
+func (s *ClientSuite) TestInitAllowsExpired(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 := s.readMeta("root.json")
+ c.Assert(err, IsNil)
+ s.withMetaExpired(func() {
+ c.Assert(client.Init(bytes), IsNil)
+ })
+}
+
+func (s *ClientSuite) TestInit(c *C) {
+ client := NewClient(MemoryLocalStore(), s.remote)
+ bytes, err := s.readMeta("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 Init() returns ErrRoleThreshold 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.Init(dataBytes), Equals, verify.ErrRoleThreshold{
+ Expected: 1, Actual: 0})
+
+ // check Update() does not return ErrNoRootKeys after initialization
+ c.Assert(client.Init(bytes), IsNil)
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+}
+
+// This is a regression test for https://github.com/theupdateframework/go-tuf/issues/370
+// where a single invalid signature resulted in an early return.
+// Instead, the client should have continued and counted the number
+// of valid signatures, ignoring the incorrect one.
+func (s *ClientSuite) TestExtraRootSignaturesOnInit(c *C) {
+ client := NewClient(MemoryLocalStore(), s.remote)
+ bytes, err := s.readMeta("root.json")
+ c.Assert(err, IsNil)
+ dataSigned := &data.Signed{}
+ c.Assert(json.Unmarshal(bytes, dataSigned), IsNil)
+
+ // check Init() succeeds when an extra invalid signature was
+ // added to the root.
+ dataSigned.Signatures = append(dataSigned.Signatures,
+ data.Signature{
+ KeyID: dataSigned.Signatures[0].KeyID,
+ Signature: make([]byte, ed25519.SignatureSize),
+ })
+ dataBytes, err := json.Marshal(dataSigned)
+ c.Assert(err, IsNil)
+ c.Assert(client.Init(dataBytes), IsNil)
+}
+
+func (s *ClientSuite) TestFirstUpdate(c *C) {
+ files, err := s.newClient(c).Update()
+ c.Assert(err, IsNil)
+ c.Assert(files, HasLen, 1)
+ assertFiles(c, files, []string{"foo.txt"})
+}
+
+func (s *ClientSuite) TestMissingRemoteMetadata(c *C) {
+ client := s.newClient(c)
+
+ s.deleteMeta("targets.json")
+ _, err := client.Update()
+ c.Assert(err, Equals, ErrMissingRemoteMetadata{"targets.json"})
+
+ s.deleteMeta("timestamp.json")
+ _, err = client.Update()
+ c.Assert(err, Equals, ErrMissingRemoteMetadata{"timestamp.json"})
+}
+
+func (s *ClientSuite) TestNoChangeUpdate(c *C) {
+ client := s.newClient(c)
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+}
+
+func (s *ClientSuite) TestNewTimestamp(c *C) {
+ client := s.updatedClient(c)
+ version := client.timestampVer
+ c.Assert(version > 0, Equals, true)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ s.syncRemote(c)
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.timestampVer > version, Equals, true)
+}
+
+func (s *ClientSuite) TestNewRoot(c *C) {
+ client := s.newClient(c)
+
+ // replace all keys
+ newKeyIDs := make(map[string][]string)
+ for role, ids := range s.keyIDs {
+ c.Assert(len(ids) > 0, Equals, true)
+ c.Assert(s.repo.RevokeKey(role, ids[0]), IsNil)
+ newKeyIDs[role] = s.genKey(c, role)
+ }
+
+ // update metadata
+ c.Assert(s.repo.Sign("targets.json"), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check update gets new root version
+ c.Assert(client.getLocalMeta(), IsNil)
+ version := client.rootVer
+ c.Assert(version > 0, Equals, true)
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.rootVer > version, Equals, true)
+
+ // check old keys are not in db
+ for _, ids := range s.keyIDs {
+ c.Assert(len(ids) > 0, Equals, true)
+ for _, id := range ids {
+ _, err := client.db.GetVerifier(id)
+ c.Assert(err, NotNil)
+ }
+ }
+
+ // check new keys are in db
+ for name, ids := range newKeyIDs {
+ c.Assert(len(ids) > 0, Equals, true)
+ for _, id := range ids {
+ verifier, err := client.db.GetVerifier(id)
+ c.Assert(err, IsNil)
+ c.Assert(verifier.MarshalPublicKey().IDs(), DeepEquals, ids)
+ }
+ role := client.db.GetRole(name)
+ c.Assert(role, NotNil)
+ c.Assert(role.KeyIDs, DeepEquals, sets.StringSliceToSet(ids))
+ }
+}
+
+// This is a regression test for https://github.com/theupdateframework/go-tuf/issues/370
+// where a single invalid signature resulted in an early return.
+// Instead, the client should have continued and counted the number
+// of valid signatures, ignoring the incorrect one.
+func (s *ClientSuite) TestExtraSignaturesOnRootUpdate(c *C) {
+ client := s.newClient(c)
+
+ // Add an extra root key to update the root to a new version.
+ s.genKey(c, "root")
+ // update metadata
+ c.Assert(s.repo.Sign("targets.json"), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // Add an extra signature to the new remote root.
+ bytes, err := s.readMeta("root.json")
+ c.Assert(err, IsNil)
+ dataSigned := &data.Signed{}
+ c.Assert(json.Unmarshal(bytes, dataSigned), IsNil)
+ dataSigned.Signatures = append(dataSigned.Signatures,
+ data.Signature{
+ KeyID: dataSigned.Signatures[0].KeyID,
+ Signature: make([]byte, ed25519.SignatureSize),
+ })
+ dataBytes, err := json.Marshal(dataSigned)
+ c.Assert(err, IsNil)
+ s.setRemoteMeta("root.json", dataBytes)
+ s.setRemoteMeta("2.root.json", dataBytes)
+
+ // check Update() succeeds when an extra invalid signature was
+ // added to the root.
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.rootVer, Equals, int64(2))
+}
+
+// startTUFRepoServer starts a HTTP server to serve a TUF Repo.
+func startTUFRepoServer(baseDir string, relPath string) (net.Listener, error) {
+ serverDir := filepath.Join(baseDir, relPath)
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ go http.Serve(l, http.FileServer(http.Dir(serverDir)))
+ return l, err
+}
+
+// newClientWithMeta creates new client and sets the root metadata for it.
+func newClientWithMeta(baseDir string, relPath string, serverAddr string) (*Client, error) {
+ initialStateDir := filepath.Join(baseDir, relPath)
+ opts := &HTTPRemoteOptions{
+ MetadataPath: "metadata",
+ TargetsPath: "targets",
+ }
+
+ remote, err := HTTPRemoteStore(fmt.Sprintf("http://%s/", serverAddr), opts, nil)
+ if err != nil {
+ return nil, err
+ }
+ c := NewClient(MemoryLocalStore(), remote)
+ for _, m := range []string{"root.json", "snapshot.json", "timestamp.json", "targets.json"} {
+ if _, err := os.Stat(initialStateDir + "/" + m); err == nil {
+ metadataJSON, err := os.ReadFile(initialStateDir + "/" + m)
+ if err != nil {
+ return nil, err
+ }
+ c.local.SetMeta(m, metadataJSON)
+ }
+ }
+ return c, nil
+}
+
+func initRootTest(c *C, baseDir string) (*Client, func() error) {
+ l, err := startTUFRepoServer(baseDir, "server")
+ c.Assert(err, IsNil)
+ tufClient, err := newClientWithMeta(baseDir, "client/metadata/current", l.Addr().String())
+ c.Assert(err, IsNil)
+ return tufClient, l.Close
+}
+
+func (s *ClientSuite) TestUpdateRoots(c *C) {
+ var tests = []struct {
+ fixturePath string
+ expectedError error
+ expectedVersions map[string]int64
+ }{
+ // Succeeds when there is no root update.
+ {"testdata/Published1Time", nil, map[string]int64{"root": 1, "timestamp": 1, "snapshot": 1, "targets": 1}},
+ // Succeeds when client only has root.json
+ {"testdata/Published1Time_client_root_only", nil, map[string]int64{"root": 1, "timestamp": 1, "snapshot": 1, "targets": 1}},
+ // Succeeds updating root from version 1 to version 2.
+ {"testdata/Published2Times_keyrotated", nil, map[string]int64{"root": 2, "timestamp": 1, "snapshot": 1, "targets": 1}},
+ // Succeeds updating root from version 1 to version 2 when the client's initial root version is expired.
+ {"testdata/Published2Times_keyrotated_initialrootexpired", nil, map[string]int64{"root": 2, "timestamp": 1, "snapshot": 1, "targets": 1}},
+ // Succeeds updating root from version 1 to version 3 when versions 1 and 2 are expired.
+ {"testdata/Published3Times_keyrotated_initialrootsexpired", nil, map[string]int64{"root": 3, "timestamp": 1, "snapshot": 1, "targets": 1}},
+ // Succeeds updating root from version 2 to version 3.
+ {"testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2", nil, map[string]int64{"root": 3, "timestamp": 1, "snapshot": 1, "targets": 1}},
+ // Fails updating root from version 1 to version 3 when versions 1 and 3 are expired but version 2 is not expired.
+ {"testdata/Published3Times_keyrotated_latestrootexpired", ErrDecodeFailed{File: "root.json", Err: verify.ErrExpired{}}, map[string]int64{"root": 2, "timestamp": 1, "snapshot": 1, "targets": 1}},
+ // Fails updating root from version 1 to version 2 when old root 1 did not sign off on it (nth root didn't sign off n+1).
+ // TODO(asraa): This testcase should have revoked the old key!
+ // https://github.com/theupdateframework/go-tuf/issues/417
+ {"testdata/Published2Times_keyrotated_invalidOldRootSignature", nil, map[string]int64{}},
+ // Fails updating root from version 1 to version 2 when the new root 2 did not sign itself (n+1th root didn't sign off n+1)
+ {"testdata/Published2Times_keyrotated_invalidNewRootSignature", verify.ErrRoleThreshold{Expected: 1, Actual: 0}, map[string]int64{}},
+ // Fails updating root to 2.root.json when the value of the version field inside it is 1 (rollback attack prevention).
+ {"testdata/Published1Time_backwardRootVersion", verify.ErrWrongVersion(verify.ErrWrongVersion{Given: 1, Expected: 2}), map[string]int64{}},
+ // Fails updating root to 2.root.json when the value of the version field inside it is 3 (rollforward attack prevention).
+ {"testdata/Published3Times_keyrotated_forwardRootVersion", verify.ErrWrongVersion(verify.ErrWrongVersion{Given: 3, Expected: 2}), map[string]int64{}},
+ // Fails updating when there is no local trusted root.
+ {"testdata/Published1Time_client_no_root", errors.New("tuf: no root keys found in local meta store"), map[string]int64{}},
+
+ // snapshot role key rotation increase the snapshot and timestamp.
+ {"testdata/Published2Times_snapshot_keyrotated", nil, map[string]int64{"root": 2, "timestamp": 2, "snapshot": 2, "targets": 1}},
+ // targets role key rotation increase the snapshot, timestamp, and targets.
+ {"testdata/Published2Times_targets_keyrotated", nil, map[string]int64{"root": 2, "timestamp": 2, "snapshot": 2, "targets": 2}},
+ // timestamp role key rotation increase the timestamp.
+ {"testdata/Published2Times_timestamp_keyrotated", nil, map[string]int64{"root": 2, "timestamp": 2, "snapshot": 1, "targets": 1}},
+ //root file size > defaultRootDownloadLimit
+ {"testdata/Published2Times_roottoolarge", ErrMetaTooLarge{Name: "2.root.json", Size: defaultRootDownloadLimit + 1, MaxSize: defaultRootDownloadLimit}, map[string]int64{}},
+ }
+
+ for _, test := range tests {
+ tufClient, closer := initRootTest(c, test.fixturePath)
+ _, err := tufClient.Update()
+ if test.expectedError == nil {
+ c.Assert(err, IsNil)
+ // Check if the root.json is being saved in non-volatile storage.
+ tufClient.getLocalMeta()
+ versionMethods := map[string]int64{"root": tufClient.rootVer,
+ "timestamp": tufClient.timestampVer,
+ "snapshot": tufClient.snapshotVer,
+ "targets": tufClient.targetsVer}
+ for m, v := range test.expectedVersions {
+ assert.Equal(c, v, versionMethods[m])
+ }
+ } else {
+ // For backward compatibility, the update root returns
+ // ErrDecodeFailed that wraps the verify.ErrExpired.
+ if _, ok := test.expectedError.(ErrDecodeFailed); ok {
+ decodeErr, ok := err.(ErrDecodeFailed)
+ c.Assert(ok, Equals, true)
+ c.Assert(decodeErr.File, Equals, "root.json")
+ _, ok = decodeErr.Err.(verify.ErrExpired)
+ c.Assert(ok, Equals, true)
+ } else {
+ assert.Equal(c, test.expectedError, err)
+ }
+ }
+ closer()
+ }
+}
+
+func (s *ClientSuite) TestFastForwardAttackRecovery(c *C) {
+ var tests = []struct {
+ fixturePath string
+ expectMetaDeleted map[string]bool
+ }{
+ // Each of the following test cases each has a two sets of TUF metadata:
+ // (1) client's initial, and (2) server's current.
+ // The naming format is PublishedTwiceMultiKeysadd_X_revoke_Y_threshold_Z_ROLE
+ // The client includes TUF metadata before key rotation for TUF ROLE with X keys.
+ // The server includes updated TUF metadata after key rotation. The
+ // rotation involves revoking Y keys from the initial keys.
+ // For each test, the TUF client's will be initialized to the client files.
+ // The test checks whether the client is able to update itself properly.
+
+ // Fast-forward recovery is not needed if less than threshold keys are revoked.
+ {"testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root",
+ map[string]bool{"root.json": false, "timestamp.json": false, "snapshot.json": false, "targets.json": false}},
+ {"testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot",
+ map[string]bool{"root.json": false, "timestamp.json": false, "snapshot.json": false, "targets.json": false}},
+ {"testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets",
+ map[string]bool{"root.json": false, "timestamp.json": false, "snapshot.json": false, "targets.json": false}},
+ {"testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp",
+ map[string]bool{"root.json": false, "timestamp.json": false, "snapshot.json": false, "targets.json": false}},
+
+ // Fast-forward recovery not needed if root keys are revoked, even when the threshold number of root keys are revoked.
+ {"testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root",
+ map[string]bool{"root.json": false, "timestamp.json": false, "snapshot.json": false, "targets.json": false}},
+
+ // Delete snapshot and timestamp metadata if a threshold number of snapshot keys are revoked.
+ {"testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot",
+ map[string]bool{"root.json": false, "timestamp.json": true, "snapshot.json": true, "targets.json": false}},
+ // Delete targets and snapshot metadata if a threshold number of targets keys are revoked.
+ {"testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets",
+ map[string]bool{"root.json": false, "timestamp.json": false, "snapshot.json": true, "targets.json": true}},
+ // Delete timestamp metadata if a threshold number of timestamp keys are revoked.
+ {"testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp",
+ map[string]bool{"root.json": false, "timestamp.json": true, "snapshot.json": false, "targets.json": false}},
+ }
+ for _, test := range tests {
+ tufClient, closer := initRootTest(c, test.fixturePath)
+ c.Assert(tufClient.UpdateRoots(), IsNil)
+ m, err := tufClient.local.GetMeta()
+ c.Assert(err, IsNil)
+ for md, deleted := range test.expectMetaDeleted {
+ if deleted {
+ if _, ok := m[md]; ok {
+ c.Fatalf("Metadata %s is not deleted!", md)
+ }
+ } else {
+ if _, ok := m[md]; !ok {
+ c.Fatalf("Metadata %s deleted!", md)
+ }
+ }
+ }
+ closer()
+ }
+
+}
+
+func (s *ClientSuite) TestUpdateRace(c *C) {
+ // Tests race condition for the client update. You need to run the test with -race flag:
+ // go test -race
+ for i := 0; i < 2; i++ {
+ go func() {
+ c := NewClient(MemoryLocalStore(), newFakeRemoteStore())
+ c.Update()
+ }()
+ }
+}
+
+func (s *ClientSuite) TestNewTargets(c *C) {
+ client := s.newClient(c)
+ files, err := client.Update()
+ c.Assert(err, IsNil)
+ assertFiles(c, files, []string{"foo.txt"})
+
+ s.addRemoteTarget(c, "bar.txt")
+ s.addRemoteTarget(c, "baz.txt")
+
+ files, err = client.Update()
+ c.Assert(err, IsNil)
+ assertFiles(c, files, []string{"bar.txt", "baz.txt"})
+
+ // Adding the same exact file should not lead to an update
+ s.addRemoteTarget(c, "bar.txt")
+ files, err = client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(files, HasLen, 0)
+}
+
+func (s *ClientSuite) TestNewTimestampKey(c *C) {
+ client := s.newClient(c)
+
+ // replace key
+ oldIDs := s.keyIDs["timestamp"]
+ c.Assert(s.repo.RevokeKey("timestamp", oldIDs[0]), IsNil)
+ newIDs := s.genKey(c, "timestamp")
+
+ // generate new snapshot (because root has changed) and timestamp
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check update gets new root and timestamp
+ c.Assert(client.getLocalMeta(), IsNil)
+ rootVer := client.rootVer
+ timestampVer := client.timestampVer
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.rootVer > rootVer, Equals, true)
+ c.Assert(client.timestampVer > timestampVer, Equals, true)
+
+ // check key has been replaced in db
+ for _, oldID := range oldIDs {
+ _, err := client.db.GetVerifier(oldID)
+ c.Assert(err, NotNil)
+ }
+ for _, newID := range newIDs {
+ verifier, err := client.db.GetVerifier(newID)
+ c.Assert(err, IsNil)
+ c.Assert(verifier.MarshalPublicKey().IDs(), DeepEquals, newIDs)
+ }
+ role := client.db.GetRole("timestamp")
+ c.Assert(role, NotNil)
+ c.Assert(role.KeyIDs, DeepEquals, sets.StringSliceToSet(newIDs))
+}
+
+func (s *ClientSuite) TestNewSnapshotKey(c *C) {
+ client := s.newClient(c)
+
+ // replace key
+ oldIDs := s.keyIDs["snapshot"]
+ c.Assert(s.repo.RevokeKey("snapshot", oldIDs[0]), IsNil)
+ newIDs := s.genKey(c, "snapshot")
+
+ // generate new snapshot and timestamp
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check update gets new root, snapshot and timestamp
+ c.Assert(client.getLocalMeta(), IsNil)
+ rootVer := client.rootVer
+ snapshotVer := client.snapshotVer
+ timestampVer := client.timestampVer
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.rootVer > rootVer, Equals, true)
+ c.Assert(client.snapshotVer > snapshotVer, Equals, true)
+ c.Assert(client.timestampVer > timestampVer, Equals, true)
+
+ // check key has been replaced in db
+ for _, oldID := range oldIDs {
+ _, err := client.db.GetVerifier(oldID)
+ c.Assert(err, NotNil)
+ }
+ for _, newID := range newIDs {
+ verifier, err := client.db.GetVerifier(newID)
+ c.Assert(err, IsNil)
+ c.Assert(verifier.MarshalPublicKey().IDs(), DeepEquals, newIDs)
+ }
+ role := client.db.GetRole("snapshot")
+ c.Assert(role, NotNil)
+ c.Assert(role.KeyIDs, DeepEquals, sets.StringSliceToSet(newIDs))
+}
+
+func (s *ClientSuite) TestNewTargetsKey(c *C) {
+ client := s.newClient(c)
+
+ // replace key
+ oldIDs := s.keyIDs["targets"]
+ c.Assert(s.repo.RevokeKey("targets", oldIDs[0]), IsNil)
+ newIDs := s.genKey(c, "targets")
+
+ // re-sign targets and generate new snapshot and timestamp
+ c.Assert(s.repo.Sign("targets.json"), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check update gets new metadata
+ c.Assert(client.getLocalMeta(), IsNil)
+ rootVer := client.rootVer
+ targetsVer := client.targetsVer
+ snapshotVer := client.snapshotVer
+ timestampVer := client.timestampVer
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.rootVer > rootVer, Equals, true)
+ c.Assert(client.targetsVer > targetsVer, Equals, true)
+ c.Assert(client.snapshotVer > snapshotVer, Equals, true)
+ c.Assert(client.timestampVer > timestampVer, Equals, true)
+
+ // check key has been replaced in db
+ for _, oldID := range oldIDs {
+ _, err := client.db.GetVerifier(oldID)
+ c.Assert(err, NotNil)
+ }
+ for _, newID := range newIDs {
+ verifier, err := client.db.GetVerifier(newID)
+ c.Assert(err, IsNil)
+ c.Assert(verifier.MarshalPublicKey().IDs(), DeepEquals, newIDs)
+ }
+ role := client.db.GetRole("targets")
+ c.Assert(role, NotNil)
+ c.Assert(role.KeyIDs, DeepEquals, sets.StringSliceToSet(newIDs))
+}
+
+func (s *ClientSuite) TestOfflineSignatureFlow(c *C) {
+ client := s.newClient(c)
+
+ // replace key
+ oldIDs := s.keyIDs["targets"]
+ c.Assert(s.repo.RevokeKey("targets", oldIDs[0]), IsNil)
+ _ = s.genKey(c, "targets")
+
+ // re-sign targets using offline flow and generate new snapshot and timestamp
+ payload, err := s.repo.Payload("targets.json")
+ c.Assert(err, IsNil)
+ signed := data.Signed{Signed: payload}
+ _, err = s.repo.SignPayload("targets", &signed)
+ c.Assert(err, IsNil)
+ for _, sig := range signed.Signatures {
+ // This method checks that the signature verifies!
+ err = s.repo.AddOrUpdateSignature("targets.json", sig)
+ c.Assert(err, IsNil)
+ }
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check update gets new metadata
+ c.Assert(client.getLocalMeta(), IsNil)
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+}
+
+func (s *ClientSuite) TestLocalExpired(c *C) {
+ client := s.newClient(c)
+
+ // locally expired timestamp.json is ok
+ version := client.timestampVer
+ c.Assert(s.repo.TimestampWithExpires(s.expiredTime), IsNil)
+ s.syncLocal(c)
+ s.withMetaExpired(func() {
+ c.Assert(client.getLocalMeta(), IsNil)
+ c.Assert(client.timestampVer > version, Equals, true)
+ })
+
+ // locally expired snapshot.json is ok
+ version = client.snapshotVer
+ c.Assert(s.repo.SnapshotWithExpires(s.expiredTime), IsNil)
+ s.syncLocal(c)
+ s.withMetaExpired(func() {
+ c.Assert(client.getLocalMeta(), IsNil)
+ c.Assert(client.snapshotVer > version, Equals, true)
+ })
+
+ // locally expired targets.json is ok
+ version = client.targetsVer
+ c.Assert(s.repo.AddTargetWithExpires("foo.txt", nil, s.expiredTime), IsNil)
+ s.syncLocal(c)
+ s.withMetaExpired(func() {
+ c.Assert(client.getLocalMeta(), IsNil)
+ c.Assert(client.targetsVer > version, Equals, true)
+ })
+
+ // locally expired root.json is not ok
+ version = client.rootVer
+ s.genKeyExpired(c, "targets")
+ s.syncLocal(c)
+ s.withMetaExpired(func() {
+ err := client.getLocalMeta()
+ if _, ok := err.(verify.ErrExpired); !ok {
+ c.Fatalf("expected err to have type signed.ErrExpired, got %T", err)
+ }
+ c.Assert(client.rootVer, Equals, version)
+ })
+}
+
+func (s *ClientSuite) TestTimestampTooLarge(c *C) {
+ s.setRemoteMeta("timestamp.json", make([]byte, defaultTimestampDownloadLimit+1))
+ _, err := s.newClient(c).Update()
+ c.Assert(err, Equals, ErrMetaTooLarge{"timestamp.json", defaultTimestampDownloadLimit + 1, defaultTimestampDownloadLimit})
+}
+
+func (s *ClientSuite) TestUpdateLocalRootExpired(c *C) {
+ client := s.newClient(c)
+
+ // add soon to expire root.json to local storage
+ s.genKeyExpired(c, "timestamp")
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncLocal(c)
+
+ // add far expiring root.json to remote storage
+ s.genKey(c, "timestamp")
+ s.addRemoteTarget(c, "bar.txt")
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ const expectedRootVersion = int64(3)
+
+ // check the update downloads the non expired remote root.json and
+ // restarts itself, thus successfully updating
+ s.withMetaExpired(func() {
+ err := client.getLocalMeta()
+ if _, ok := err.(verify.ErrExpired); !ok {
+ c.Fatalf("expected err to have type signed.ErrExpired, got %T", err)
+ }
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.rootVer, Equals, expectedRootVersion)
+ })
+}
+
+func (s *ClientSuite) TestUpdateRemoteExpired(c *C) {
+ client := s.updatedClient(c)
+
+ // expired remote metadata should always be rejected
+ c.Assert(s.repo.TimestampWithExpires(s.expiredTime), IsNil)
+ s.syncRemote(c)
+ s.withMetaExpired(func() {
+ _, err := client.Update()
+ s.assertErrExpired(c, err, "timestamp.json")
+ })
+
+ c.Assert(s.repo.SnapshotWithExpires(s.expiredTime), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+ s.withMetaExpired(func() {
+ _, err := client.Update()
+ s.assertErrExpired(c, err, "snapshot.json")
+ })
+
+ c.Assert(s.repo.AddTargetWithExpires("bar.txt", nil, s.expiredTime), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ s.syncRemote(c)
+ s.withMetaExpired(func() {
+ _, err := client.Update()
+ s.assertErrExpired(c, err, "targets.json")
+ })
+
+ s.genKeyExpired(c, "timestamp")
+ c.Assert(s.repo.RemoveTarget("bar.txt"), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+ s.withMetaExpired(func() {
+ _, err := client.Update()
+ s.assertErrExpired(c, err, "root.json")
+ })
+}
+
+func (s *ClientSuite) TestUpdateLocalRootExpiredKeyChange(c *C) {
+ client := s.newClient(c)
+
+ // add soon to expire root.json to local storage
+ s.genKeyExpired(c, "timestamp")
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncLocal(c)
+
+ // replace all keys
+ newKeyIDs := make(map[string][]string)
+ for role, ids := range s.keyIDs {
+ if role != "snapshot" && role != "timestamp" && role != "targets" {
+ c.Assert(s.repo.RevokeKey(role, ids[0]), IsNil)
+ newKeyIDs[role] = s.genKey(c, role)
+ }
+ }
+
+ // update metadata
+ c.Assert(s.repo.Sign("targets.json"), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check the update downloads the non expired remote root.json and
+ // restarts itself, thus successfully updating
+ s.withMetaExpired(func() {
+ err := client.getLocalMeta()
+ c.Assert(err, FitsTypeOf, verify.ErrExpired{})
+
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+ })
+}
+
+func (s *ClientSuite) TestUpdateMixAndMatchAttack(c *C) {
+ // generate metadata with an explicit expires so we can make predictable changes
+ expires := time.Now().Add(time.Hour)
+ c.Assert(s.repo.AddTargetWithExpires("foo.txt", nil, expires), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+ client := s.updatedClient(c)
+
+ // grab the remote targets.json
+ oldTargets, err := s.readMeta("targets.json")
+ if err != nil {
+ c.Fatal("missing remote targets.json")
+ }
+
+ // generate new remote metadata, but replace targets.json with the old one
+ c.Assert(s.repo.AddTargetWithExpires("bar.txt", nil, expires), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+ newTargets, err := s.readMeta("targets.json")
+ if err != nil {
+ c.Fatal("missing remote targets.json")
+ }
+ s.setRemoteMeta("targets.json", oldTargets)
+
+ // check update returns ErrWrongSize for targets.json
+ _, err = client.Update()
+ c.Assert(err, DeepEquals, ErrWrongSize{"targets.json", int64(len(oldTargets)), int64(len(newTargets))})
+
+ // do the same but keep the size the same
+ c.Assert(s.repo.RemoveTargetWithExpires("foo.txt", expires), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ s.syncRemote(c)
+ s.setRemoteMeta("targets.json", oldTargets)
+
+ // check update returns ErrWrongHash
+ _, err = client.Update()
+ assertWrongHash(c, err)
+}
+
+func (s *ClientSuite) TestUpdateReplayAttack(c *C) {
+ client := s.updatedClient(c)
+
+ // grab the remote timestamp.json
+ oldTimestamp, err := s.readMeta("timestamp.json")
+ if err != nil {
+ c.Fatal("missing remote timestamp.json")
+ }
+
+ // generate a new timestamp and sync with the client
+ version := client.timestampVer
+ c.Assert(version > 0, Equals, true)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ s.syncRemote(c)
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.timestampVer > version, Equals, true)
+
+ // replace remote timestamp.json with the old one
+ s.setRemoteMeta("timestamp.json", oldTimestamp)
+
+ // check update returns ErrLowVersion
+ _, err = client.Update()
+ c.Assert(err, DeepEquals, ErrDecodeFailed{
+ File: "timestamp.json",
+ Err: verify.ErrLowVersion{
+ Actual: version,
+ Current: client.timestampVer,
+ },
+ })
+}
+
+func (s *ClientSuite) TestUpdateForkTimestamp(c *C) {
+ client := s.updatedClient(c)
+
+ // grab the remote timestamp.json
+ oldTimestamp, err := s.readMeta("timestamp.json")
+ if err != nil {
+ c.Fatal("missing remote timestamp.json")
+ }
+
+ // generate a new timestamp and sync with the client
+ version := client.timestampVer
+ c.Assert(version > 0, Equals, true)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ s.syncRemote(c)
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+ newVersion := client.timestampVer
+ c.Assert(newVersion > version, Equals, true)
+
+ // generate a new, different timestamp with the *same version*
+ s.setRemoteMeta("timestamp.json", oldTimestamp)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(client.timestampVer, Equals, newVersion) // double-check: same version?
+ s.syncRemote(c)
+
+ oldMeta, err := client.local.GetMeta()
+ c.Assert(err, IsNil)
+ _, err = client.Update()
+ c.Assert(err, IsNil) // no error: the targets.json version didn't change, so there was no update!
+ // Client shouldn't update!
+ newMeta, err := client.local.GetMeta()
+ c.Assert(err, IsNil)
+ c.Assert(oldMeta, DeepEquals, newMeta)
+}
+
+func (s *ClientSuite) TestUpdateTamperedTargets(c *C) {
+ client := s.newClient(c)
+
+ // get local targets.json
+ meta, err := s.store.GetMeta()
+ c.Assert(err, IsNil)
+ targetsJSON, ok := meta["targets.json"]
+ if !ok {
+ c.Fatal("missing targets.json")
+ }
+
+ type signedTargets struct {
+ Signed data.Targets `json:"signed"`
+ Signatures []data.Signature `json:"signatures"`
+ }
+ targets := &signedTargets{}
+ c.Assert(json.Unmarshal(targetsJSON, targets), IsNil)
+
+ // update remote targets.json to have different content but same size
+ targets.Signed.Type = "xxxxxxx"
+ tamperedJSON, err := json.Marshal(targets)
+ c.Assert(err, IsNil)
+ s.store.SetMeta("targets.json", tamperedJSON)
+ s.store.Commit(false, nil, nil)
+ s.syncRemote(c)
+ _, err = client.Update()
+ assertWrongHash(c, err)
+
+ // update remote targets.json to have the wrong size
+ targets.Signed.Type = "xxx"
+ tamperedJSON, err = json.Marshal(targets)
+ c.Assert(err, IsNil)
+ s.store.SetMeta("targets.json", tamperedJSON)
+ s.store.Commit(false, nil, nil)
+ c.Assert(s.repo.Timestamp(), IsNil) // unless timestamp changes, the client doesn't even look at "targets.json"
+ s.syncRemote(c)
+ _, err = client.Update()
+ c.Assert(err, DeepEquals, ErrWrongSize{"targets.json", int64(len(tamperedJSON)), int64(len(targetsJSON))})
+}
+
+func (s *ClientSuite) TestUpdateHTTP(c *C) {
+ tmp := c.MkDir()
+
+ // start file server
+ addr, cleanup := startFileServer(c, tmp)
+ defer cleanup()
+
+ for _, consistentSnapshot := range []bool{false, true} {
+ dir := fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot)
+
+ // generate repository
+ repo := generateRepoFS(c, filepath.Join(tmp, dir), targetFiles, consistentSnapshot)
+
+ // initialize a client
+ remote, err := HTTPRemoteStore(fmt.Sprintf("http://%s/%s/repository", addr, dir), nil, nil)
+ c.Assert(err, IsNil)
+ client := NewClient(MemoryLocalStore(), remote)
+ rootMeta, err := repo.SignedMeta("root.json")
+ c.Assert(err, IsNil)
+ rootJsonBytes, err := json.Marshal(rootMeta)
+ c.Assert(err, IsNil)
+ c.Assert(client.Init(rootJsonBytes), IsNil)
+
+ // check update is ok
+ targets, err := client.Update()
+ c.Assert(err, IsNil)
+ assertFiles(c, targets, []string{"foo.txt", "bar.txt", "baz.txt"})
+
+ // check can download files
+ for name, data := range targetFiles {
+ var dest testDestination
+ c.Assert(client.Download(name, &dest), IsNil)
+ c.Assert(dest.deleted, Equals, false)
+ c.Assert(dest.String(), Equals, string(data))
+ }
+ }
+}
+
+// TestRollbackSnapshot tests a rollback version of snapshot.
+func (s *ClientSuite) TestRollbackSnapshot(c *C) {
+ client := s.updatedClient(c)
+
+ // generate a new snapshot & timestamp v2 and sync with the client
+ version := client.snapshotVer
+ c.Assert(version > 0, Equals, true)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.snapshotVer > version, Equals, true)
+
+ // replace remote snapshot.json with old version and timestamp again.
+ s.repo.SetSnapshotVersion(version)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check update returns ErrLowVersion
+ _, err = client.Update()
+
+ c.Assert(err, DeepEquals, verify.ErrLowVersion{
+ Actual: version,
+ Current: client.snapshotVer,
+ })
+}
+
+func (s *ClientSuite) TestRollbackTopLevelTargets(c *C) {
+ client := s.updatedClient(c)
+
+ // generate a new targets and sync with the client
+ version := client.targetsVer
+ c.Assert(version > 0, Equals, true)
+ s.addRemoteTarget(c, "bar.txt")
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(client.targetsVer > version, Equals, true)
+
+ // replace remote snapshot.json with old version and timestamp again.
+ s.repo.SetTargetsVersion(version)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check update returns ErrLowVersion
+ _, err = client.Update()
+ c.Assert(err, DeepEquals, verify.ErrLowVersion{
+ Actual: version,
+ Current: client.targetsVer,
+ })
+}
+
+func (s *ClientSuite) TestRollbackDelegatedTargets(c *C) {
+ client := s.updatedClient(c)
+ // add a delegation
+ signer, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ role := data.DelegatedRole{
+ Name: "role",
+ KeyIDs: signer.PublicData().IDs(),
+ Paths: []string{"bar.txt", "baz.txt"},
+ Threshold: 1,
+ }
+ s.store.SaveSigner("role", signer)
+ s.repo.AddDelegatedRole("targets", role, []*data.PublicKey{signer.PublicData()})
+ s.repo.AddTargetToPreferredRole("bar.txt", nil, "role")
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // save v1 delegation
+ meta, err := s.store.GetMeta()
+ c.Assert(err, IsNil)
+ oldRole, ok := meta["role.json"]
+ if !ok {
+ c.Fatal("missing role.json")
+ }
+ // update client and verify download delegated target
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+ var dest testDestination
+ c.Assert(client.Download("bar.txt", &dest), IsNil)
+
+ // update delegation to v2
+ s.repo.AddTargetToPreferredRole("baz.txt", nil, "role")
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // update client and verify download v2 delegated target
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(dest.Delete(), IsNil)
+ c.Assert(client.Download("baz.txt", &dest), IsNil)
+
+ // rollback role.json version.
+ c.Assert(s.store.SetMeta("role.json", oldRole), IsNil)
+ repo, err := tuf.NewRepo(s.store)
+ c.Assert(err, IsNil)
+ c.Assert(repo.Snapshot(), IsNil)
+ c.Assert(repo.Timestamp(), IsNil)
+ c.Assert(repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // check update returns ErrLowVersion
+ _, err = client.Update()
+ c.Assert(err, DeepEquals, verify.ErrLowVersion{
+ Actual: 1,
+ Current: 2,
+ })
+}
+
+type testDestination struct {
+ bytes.Buffer
+ deleted bool
+}
+
+func (t *testDestination) Delete() error {
+ t.deleted = true
+ return nil
+}
+
+func (s *ClientSuite) TestDownloadUnknownTarget(c *C) {
+ client := s.updatedClient(c)
+ var dest testDestination
+ c.Assert(client.Download("nonexistent", &dest), Equals, ErrUnknownTarget{Name: "nonexistent", SnapshotVersion: 1})
+ c.Assert(dest.deleted, Equals, true)
+}
+
+func (s *ClientSuite) TestDownloadNoExist(c *C) {
+ client := s.updatedClient(c)
+ s.deleteTarget("foo.txt")
+ var dest testDestination
+ c.Assert(client.Download("foo.txt", &dest), Equals, ErrNotFound{"foo.txt"})
+ c.Assert(dest.deleted, Equals, true)
+}
+
+func (s *ClientSuite) TestDownloadOK(c *C) {
+ client := s.updatedClient(c)
+ // the filename is normalized if necessary
+ for _, name := range []string{"/foo.txt", "foo.txt"} {
+ var dest testDestination
+ c.Assert(client.Download(name, &dest), IsNil)
+ c.Assert(dest.deleted, Equals, false)
+ c.Assert(dest.String(), Equals, "foo")
+ }
+}
+
+func (s *ClientSuite) TestDownloadWrongSize(c *C) {
+ client := s.updatedClient(c)
+ // Update with a file that's incorrect size.
+ s.setRemoteTarget("foo.txt", []byte("wrong-size"))
+ var dest testDestination
+ c.Assert(client.Download("foo.txt", &dest), DeepEquals, ErrWrongSize{"foo.txt", 10, 3})
+ c.Assert(dest.deleted, Equals, true)
+}
+
+func (s *ClientSuite) TestDownloadTargetTooLong(c *C) {
+ client := s.updatedClient(c)
+ s.setRemoteTarget("foo.txt", []byte("foo-ooo"))
+ var dest testDestination
+ c.Assert(client.Download("foo.txt", &dest), DeepEquals, ErrWrongSize{"foo.txt", 7, 3})
+ c.Assert(dest.deleted, Equals, true)
+}
+
+func (s *ClientSuite) TestDownloadTargetTooShort(c *C) {
+ client := s.updatedClient(c)
+ s.setRemoteTarget("foo.txt", []byte("fo"))
+ var dest testDestination
+ c.Assert(client.Download("foo.txt", &dest), DeepEquals, ErrWrongSize{"foo.txt", 2, 3})
+ c.Assert(dest.deleted, Equals, true)
+}
+
+func (s *ClientSuite) TestDownloadTargetCorruptData(c *C) {
+ client := s.updatedClient(c)
+ s.setRemoteTarget("foo.txt", []byte("ooo"))
+ var dest testDestination
+ assertWrongHash(c, client.Download("foo.txt", &dest))
+ c.Assert(dest.deleted, Equals, true)
+}
+
+func (s *ClientSuite) TestAvailableTargets(c *C) {
+ client := s.updatedClient(c)
+ files, err := client.Targets()
+ c.Assert(err, IsNil)
+ assertFiles(c, files, []string{"foo.txt"})
+
+ s.addRemoteTarget(c, "bar.txt")
+ s.addRemoteTarget(c, "baz.txt")
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+ files, err = client.Targets()
+ c.Assert(err, IsNil)
+ assertFiles(c, files, []string{"foo.txt", "bar.txt", "baz.txt"})
+}
+
+func (s *ClientSuite) TestAvailableTarget(c *C) {
+ client := s.updatedClient(c)
+
+ target, err := client.Target("foo.txt")
+ c.Assert(err, IsNil)
+ assertFile(c, target, "foo.txt")
+
+ target, err = client.Target("/foo.txt")
+ c.Assert(err, IsNil)
+ assertFile(c, target, "foo.txt")
+
+ _, err = client.Target("bar.txt")
+ c.Assert(err, Equals, ErrNotFound{"bar.txt"})
+
+ _, err = client.Target("/bar.txt")
+ c.Assert(err, Equals, ErrNotFound{"/bar.txt"})
+}
+
+func (s *ClientSuite) TestUnknownKeyIDs(c *C) {
+ // get local root.json
+ meta, err := s.store.GetMeta()
+ c.Assert(err, IsNil)
+
+ rootJSON, ok := meta["root.json"]
+ c.Assert(ok, Equals, true)
+
+ var root struct {
+ Signed data.Root `json:"signed"`
+ Signatures []data.Signature `json:"signatures"`
+ }
+ c.Assert(json.Unmarshal(rootJSON, &root), IsNil)
+
+ // update remote root.json to add a new key with an unknown id
+ signer, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+
+ root.Signed.Keys["unknown-key-id"] = signer.PublicData()
+
+ // re-sign the root metadata, then commit it back into the store.
+ signingKeys, err := s.store.GetSigners("root")
+ c.Assert(err, IsNil)
+
+ signedRoot, err := sign.Marshal(root.Signed, signingKeys...)
+ c.Assert(err, IsNil)
+
+ rootJSON, err = cjson.EncodeCanonical(signedRoot)
+ c.Assert(err, IsNil)
+
+ s.store.SetMeta("root.json", rootJSON)
+ s.store.Commit(false, nil, nil)
+ s.syncRemote(c)
+
+ // FIXME(TUF-0.9) We need this for now because the client still uses
+ // the TUF-0.9 update workflow, where we decide to update the root
+ // metadata when we observe a new root through the snapshot.
+ repo, err := tuf.NewRepo(s.store)
+ c.Assert(err, IsNil)
+ c.Assert(repo.Snapshot(), IsNil)
+ c.Assert(repo.Timestamp(), IsNil)
+ c.Assert(repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ // Make sure the client can update with the unknown keyid.
+ client := s.newClient(c)
+ _, err = client.Update()
+ c.Assert(err, IsNil)
+}
+
+func generateRepoFS(c *C, dir string, files map[string][]byte, consistentSnapshot bool) *tuf.Repo {
+ repo, err := tuf.NewRepo(tuf.FileSystemStore(dir, nil))
+ c.Assert(err, IsNil)
+ if !consistentSnapshot {
+ c.Assert(repo.Init(false), IsNil)
+ }
+ for _, role := range []string{"root", "snapshot", "targets", "timestamp"} {
+ _, err := repo.GenKey(role)
+ c.Assert(err, IsNil)
+ }
+ for file, data := range files {
+ path := filepath.Join(dir, "staged", "targets", file)
+ c.Assert(os.MkdirAll(filepath.Dir(path), 0755), IsNil)
+ c.Assert(os.WriteFile(path, data, 0644), IsNil)
+ c.Assert(repo.AddTarget(file, nil), IsNil)
+ }
+ c.Assert(repo.Snapshot(), IsNil)
+ c.Assert(repo.Timestamp(), IsNil)
+ c.Assert(repo.Commit(), IsNil)
+ return repo
+}
+
+func (s *ClientSuite) TestVerifyDigest(c *C) {
+ digest := "sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"
+ hash := "bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"
+ size := int64(42)
+
+ c.Assert(s.repo.AddTargetsWithDigest(hash, "sha256", size, digest, nil), IsNil)
+ c.Assert(s.repo.Snapshot(), IsNil)
+ c.Assert(s.repo.Timestamp(), IsNil)
+ c.Assert(s.repo.Commit(), IsNil)
+ s.syncRemote(c)
+
+ client := s.newClient(c)
+ _, err := client.Update()
+ c.Assert(err, IsNil)
+
+ c.Assert(client.VerifyDigest(hash, "sha256", size, digest), IsNil)
+}
+
+type StateLessSuite struct{}
+
+var _ = Suite(&StateLessSuite{})
+
+func (s *StateLessSuite) TestRejectsMultiSignaturesSameKeyDifferentIDs(c *C) {
+ // In this test Alice and Bob want to create a TUF repo
+ // where a root key rotation would require both their signatures.
+ // Alice uses an old version of Go-TUF where each key gets assigned several IDs.
+ // Bob uses a modern version of Go-TUF that does not produce the same list of IDs for a same key.
+ // This test checks that the TUF client
+ // will not accept a root rotation
+ // signed twice with Alice's key with different key IDs each time.
+ // This test was failing with https://github.com/theupdateframework/go-tuf/tree/ac7b5d7bce18cca5a84a28b021bd6372f450b35b
+ // because the signature verification code was assuming that the key IDs used in the metadata
+ // were the same as the one the TUF library of the client would generate,
+ // breaking the security of threshold signatures.
+
+ // The attack works just the same if Alice is malicious from the beginning
+ // and convinces Bob to sign an initial "root.json"
+ // with additional key IDs for her only key,
+ // but this scenario show that the vulnerability can even impact situations
+ // where Alice is not malicious at all,
+ // she was simply using an old client and an attacker stole her key.
+ // The purpose of threshold signatures in TUF is precisely
+ // to make sure that an attacker cannot forge signatures
+ // if they did not steal a large enough number of keys.
+
+ alice, err := keys.GenerateEd25519Key()
+ if err != nil {
+ panic(err)
+ }
+
+ root := data.NewRoot()
+ root.Version = 1
+ root.Roles["root"] = &data.Role{
+ KeyIDs: []string{},
+ Threshold: 2, // Note the threshold
+ }
+
+ // reproduces how IDs were computed in
+ // https://github.com/theupdateframework/go-tuf/blob/8e84384bebe3/data/types.go#L50
+ oldTUFIDs := func(k *data.PublicKey) []string {
+ bytes, _ := cjson.EncodeCanonical(k)
+ digest := sha256.Sum256(bytes)
+ ids := []string{hex.EncodeToString(digest[:])}
+
+ if k.Scheme != "" || len(k.Algorithms) != 0 {
+ bytes, _ = cjson.EncodeCanonical(&data.PublicKey{
+ Type: k.Type,
+ Value: k.Value,
+ })
+ digest = sha256.Sum256(bytes)
+ ids = append(ids, hex.EncodeToString(digest[:]))
+ }
+
+ return ids
+ }
+
+ // Alice adds her key using an old version of go-tuf
+ // which will use several IDs
+ for _, keyID := range oldTUFIDs(alice.PublicData()) {
+ root.Keys[keyID] = alice.PublicData()
+ root.Roles["root"].KeyIDs = append(root.Roles["root"].KeyIDs, keyID)
+ }
+
+ bob, err := keys.GenerateEd25519Key()
+ if err != nil {
+ panic(err)
+ }
+
+ root.AddKey(bob.PublicData())
+ root.Roles["root"].KeyIDs = append(
+ root.Roles["root"].KeyIDs,
+ bob.PublicData().IDs()...,
+ )
+
+ // signer for the other roles, not important
+ delegatedSigner, _ := keys.GenerateEd25519Key()
+ root.AddKey(delegatedSigner.PublicData())
+ for _, role := range []string{"targets", "snapshot", "timestamp"} {
+ root.Roles[role] = &data.Role{
+ KeyIDs: delegatedSigner.PublicData().IDs(),
+ Threshold: 1,
+ }
+ }
+
+ signedRoot, err := sign.Marshal(root, alice, bob)
+ c.Assert(err, IsNil)
+ rootJSON, err := json.Marshal(signedRoot)
+ c.Assert(err, IsNil)
+
+ // producing evil root using only Alice's key
+
+ evilRoot := root
+ evilRoot.Version = 2
+
+ canonical, err := cjson.EncodeCanonical(evilRoot)
+ c.Assert(err, IsNil)
+ sig, err := alice.SignMessage(canonical)
+ c.Assert(err, IsNil)
+ signedEvilRoot := &data.Signed{
+ Signed: canonical,
+ Signatures: make([]data.Signature, 0),
+ }
+ for _, keyID := range oldTUFIDs(alice.PublicData()) {
+ signedEvilRoot.Signatures = append(signedEvilRoot.Signatures, data.Signature{
+ Signature: sig,
+ KeyID: keyID,
+ })
+ }
+ evilRootJSON, err := json.Marshal(signedEvilRoot)
+ c.Assert(err, IsNil)
+
+ // checking that client does not accept root rotation
+ // to evil root
+
+ localStore := MemoryLocalStore()
+ err = localStore.SetMeta("root.json", rootJSON)
+ c.Assert(err, IsNil)
+
+ remoteStore := newFakeRemoteStore()
+ remoteStore.meta["2.root.json"] = newFakeFile(evilRootJSON)
+
+ client := NewClient(localStore, remoteStore)
+
+ err = client.UpdateRoots()
+ if err != nil {
+ c.Assert(err, DeepEquals, verify.ErrRoleThreshold{Expected: 2, Actual: 1})
+ } else {
+ c.Fatalf("client returned no error when updating with evil root")
+ }
+}
diff --git a/client/delegations.go b/client/delegations.go
new file mode 100644
index 0000000..cc0fc48
--- /dev/null
+++ b/client/delegations.go
@@ -0,0 +1,152 @@
+package client
+
+import (
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/pkg/targets"
+ "github.com/theupdateframework/go-tuf/verify"
+)
+
+// getTargetFileMeta searches for a verified TargetFileMeta matching a target
+// Requires a local snapshot to be loaded and is locked to the snapshot versions.
+func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) {
+ snapshot, err := c.loadLocalSnapshot()
+ if err != nil {
+ return data.TargetFileMeta{}, err
+ }
+
+ targetFileMeta, _, err := c.getTargetFileMetaDelegationPath(target, snapshot)
+ if err != nil {
+ return data.TargetFileMeta{}, err
+ }
+ return targetFileMeta, nil
+}
+
+// getTargetFileMetaDelegationPath searches for a verified TargetFileMeta matching a target
+// Requires snapshot to be passed and is locked to that specific snapshot versions.
+// Searches through delegated targets following TUF spec 1.0.19 section 5.6.
+func (c *Client) getTargetFileMetaDelegationPath(target string, snapshot *data.Snapshot) (data.TargetFileMeta, []string, error) {
+ // delegationsIterator covers 5.6.7
+ // - pre-order depth-first search starting with the top targets
+ // - filter delegations with paths or path_hash_prefixes matching searched target
+ // - 5.6.7.1 cycles protection
+ // - 5.6.7.2 terminations
+ delegations, err := targets.NewDelegationsIterator(target, c.db)
+ if err != nil {
+ return data.TargetFileMeta{}, nil, err
+ }
+
+ targetFileMeta := data.TargetFileMeta{}
+ delegationRole := ""
+
+ for i := 0; i < c.MaxDelegations; i++ {
+ d, ok := delegations.Next()
+ if !ok {
+ return data.TargetFileMeta{}, nil, ErrUnknownTarget{target, snapshot.Version}
+ }
+
+ // covers 5.6.{1,2,3,4,5,6}
+ targets, err := c.loadDelegatedTargets(snapshot, d.Delegatee.Name, d.DB)
+ if err != nil {
+ return data.TargetFileMeta{}, nil, err
+ }
+
+ // stop when the searched TargetFileMeta is found
+ if m, ok := targets.Targets[target]; ok {
+ delegationRole = d.Delegatee.Name
+ targetFileMeta = m
+ break
+ }
+
+ if targets.Delegations != nil {
+ delegationsDB, err := verify.NewDBFromDelegations(targets.Delegations)
+ if err != nil {
+ return data.TargetFileMeta{}, nil, err
+ }
+ err = delegations.Add(targets.Delegations.Roles, d.Delegatee.Name, delegationsDB)
+ if err != nil {
+ return data.TargetFileMeta{}, nil, err
+ }
+ }
+ }
+
+ if len(delegationRole) > 0 {
+ return targetFileMeta, buildPath(delegations.Parent, delegationRole, ""), nil
+ }
+
+ return data.TargetFileMeta{}, nil, ErrMaxDelegations{
+ Target: target,
+ MaxDelegations: c.MaxDelegations,
+ SnapshotVersion: snapshot.Version,
+ }
+}
+
+func buildPath(parent func(string) string, start string, end string) []string {
+ if start == end {
+ return nil
+ }
+
+ path := []string{start}
+ current := start
+ for {
+ current = parent(current)
+ if current == end {
+ break
+ }
+ path = append(path, current)
+ }
+ return path
+}
+
+func (c *Client) loadLocalSnapshot() (*data.Snapshot, error) {
+ if err := c.getLocalMeta(); err != nil {
+ return nil, err
+ }
+ rawS, ok := c.localMeta["snapshot.json"]
+ if !ok {
+ return nil, ErrNoLocalSnapshot
+ }
+
+ snapshot := &data.Snapshot{}
+ if err := c.db.Unmarshal(rawS, snapshot, "snapshot", c.snapshotVer); err != nil {
+ return nil, ErrDecodeFailed{"snapshot.json", err}
+ }
+ return snapshot, nil
+}
+
+// loadDelegatedTargets downloads, decodes, verifies and stores targets
+func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, db *verify.DB) (*data.Targets, error) {
+ var err error
+ fileName := role + ".json"
+ fileMeta, ok := snapshot.Meta[fileName]
+ if !ok {
+ return nil, ErrRoleNotInSnapshot{role, snapshot.Version}
+ }
+
+ // 5.6.1 download target if not in the local store
+ // 5.6.2 check against snapshot hash
+ // 5.6.4 check against snapshot version
+ raw, alreadyStored := c.localMetaFromSnapshot(fileName, fileMeta)
+ if !alreadyStored {
+ raw, err = c.downloadMetaFromSnapshot(fileName, fileMeta)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ targets := &data.Targets{}
+ // 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
+ err = db.Unmarshal(raw, targets, role, fileMeta.Version)
+ if err != nil {
+ return nil, ErrDecodeFailed{fileName, err}
+ }
+
+ // 5.6.6 persist
+ if !alreadyStored {
+ if err := c.local.SetMeta(fileName, raw); err != nil {
+ return nil, err
+ }
+ }
+ return targets, nil
+}
diff --git a/client/delegations_test.go b/client/delegations_test.go
new file mode 100644
index 0000000..6d5a776
--- /dev/null
+++ b/client/delegations_test.go
@@ -0,0 +1,316 @@
+package client
+
+import (
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/util"
+ "github.com/theupdateframework/go-tuf/verify"
+)
+
+func TestGetTargetMeta(t *testing.T) {
+ verify.IsExpired = func(t time.Time) bool { return false }
+ c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
+ defer func() { assert.Nil(t, closer()) }()
+ _, err := c.Update()
+ assert.Nil(t, err)
+
+ f, err := c.getTargetFileMeta("f.txt")
+ assert.Nil(t, err)
+ 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) {
+ verify.IsExpired = func(t time.Time) bool { return false }
+ c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
+ defer func() { assert.Nil(t, closer()) }()
+ _, err := c.Update()
+ assert.Nil(t, err)
+ c.MaxDelegations = 2
+ _, err = c.getTargetFileMeta("c.txt")
+ assert.Equal(t, ErrMaxDelegations{Target: "c.txt", MaxDelegations: 2, SnapshotVersion: 2}, err)
+}
+
+func TestMetaNotFound(t *testing.T) {
+ verify.IsExpired = func(t time.Time) bool { return false }
+ c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
+ defer func() { assert.Nil(t, closer()) }()
+ _, err := c.Update()
+ assert.Nil(t, err)
+ _, err = c.getTargetFileMeta("unknown.txt")
+ assert.Equal(t, ErrUnknownTarget{Name: "unknown.txt", SnapshotVersion: 2}, err)
+}
+
+type fakeRemote struct {
+ getMeta func(name string) (stream io.ReadCloser, size int64, err error)
+ getTarget func(path string) (stream io.ReadCloser, size int64, err error)
+}
+
+func (f fakeRemote) GetMeta(name string) (stream io.ReadCloser, size int64, err error) {
+ return f.getMeta(name)
+}
+
+func (f fakeRemote) GetTarget(name string) (stream io.ReadCloser, size int64, err error) {
+ return f.getTarget(name)
+}
+
+func TestTargetsNotFound(t *testing.T) {
+ verify.IsExpired = func(t time.Time) bool { return false }
+ c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
+ defer func() { assert.Nil(t, closer()) }()
+ _, err := c.Update()
+ assert.Nil(t, err)
+
+ previousRemote := c.remote
+ newRemote := fakeRemote{
+ getMeta: func(path string) (stream io.ReadCloser, size int64, err error) {
+ if path == "1.c.json" {
+ return nil, 0, ErrNotFound{}
+ }
+ return previousRemote.GetMeta(path)
+ },
+ getTarget: previousRemote.GetTarget,
+ }
+ c.remote = newRemote
+
+ _, err = c.getTargetFileMeta("c.txt")
+ assert.Equal(t, ErrMissingRemoteMetadata{Name: "c.json"}, err)
+}
+
+func TestUnverifiedTargets(t *testing.T) {
+ verify.IsExpired = func(t time.Time) bool { return false }
+ c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
+ defer closer()
+ _, err := c.Update()
+ assert.Nil(t, err)
+
+ previousRemote := c.remote
+ newRemote := fakeRemote{
+ getMeta: func(path string) (stream io.ReadCloser, size int64, err error) {
+ if path == "1.c.json" {
+ // returns a snapshot that does not match
+ return previousRemote.GetMeta("1.d.json")
+ }
+ return previousRemote.GetMeta(path)
+ },
+ getTarget: previousRemote.GetTarget,
+ }
+ c.remote = newRemote
+
+ _, err = c.getTargetFileMeta("c.txt")
+ assert.Equal(t, ErrDecodeFailed{File: "c.json", Err: verify.ErrRoleThreshold{Expected: 1, Actual: 0}}, err)
+}
+
+func TestPersistedMeta(t *testing.T) {
+ verify.IsExpired = func(t time.Time) bool { return false }
+ c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
+ defer closer()
+ _, err := c.Update()
+ assert.Nil(t, err)
+
+ _, err = c.local.GetMeta()
+ assert.Nil(t, err)
+
+ type expectedTargets struct {
+ name string
+ version int64
+ }
+ var persistedTests = []struct {
+ file string
+ targets []expectedTargets
+ downloadError error
+ targetError error
+ fileContent string
+ }{
+ {
+ file: "unknown",
+ targets: []expectedTargets{
+ {
+ name: "targets.json",
+ version: 2,
+ },
+ },
+ downloadError: ErrUnknownTarget{Name: "unknown", SnapshotVersion: 2},
+ targetError: ErrNotFound{File: "unknown"},
+ fileContent: "",
+ },
+ {
+ file: "b.txt",
+ targets: []expectedTargets{
+ {
+ name: "targets.json",
+ version: 2,
+ },
+ {
+ name: "a.json",
+ version: 1,
+ },
+ {
+ name: "b.json",
+ version: 1,
+ },
+ },
+ downloadError: nil,
+ targetError: nil,
+ fileContent: "Contents: b.txt",
+ },
+ {
+ file: "f.txt",
+ targets: []expectedTargets{
+ {
+ name: "targets.json",
+ version: 2,
+ },
+ {
+ name: "a.json",
+ version: 1,
+ },
+ {
+ name: "b.json",
+ version: 1,
+ },
+ {
+ name: "c.json",
+ version: 1,
+ },
+ {
+ name: "d.json",
+ version: 1,
+ },
+ {
+ name: "e.json",
+ version: 1,
+ },
+ {
+ name: "f.json",
+ version: 1,
+ },
+ },
+ downloadError: nil,
+ targetError: nil,
+ fileContent: "Contents: f.txt",
+ },
+ }
+
+ for _, tt := range persistedTests {
+ t.Run("search "+tt.file, func(t *testing.T) {
+ var dest testDestination
+ err = c.Download(tt.file, &dest)
+ assert.Equal(t, tt.downloadError, err)
+ assert.Equal(t, tt.fileContent, dest.String())
+
+ target, err := c.Target(tt.file)
+ assert.Equal(t, tt.targetError, err)
+ if tt.targetError == nil {
+ meta, err := util.GenerateTargetFileMeta(strings.NewReader(tt.fileContent), target.HashAlgorithms()...)
+ assert.Nil(t, err)
+ assert.Nil(t, util.TargetFileMetaEqual(target, meta))
+ }
+
+ p, err := c.local.GetMeta()
+ assert.Nil(t, err)
+ persisted := copyStore(p)
+ persistedLocal := copyStore(c.localMeta)
+ // trim non targets metas
+ for _, notTargets := range []string{"root.json", "snapshot.json", "timestamp.json"} {
+ delete(persisted, notTargets)
+ delete(persistedLocal, notTargets)
+ }
+ for _, targets := range tt.targets {
+ // Test local store
+ storedVersion, err := versionOfStoredTargets(targets.name, persisted)
+ assert.Equal(t, targets.version, storedVersion)
+ assert.Nil(t, err)
+ delete(persisted, targets.name)
+
+ // Test localMeta
+ storedVersion, err = versionOfStoredTargets(targets.name, persistedLocal)
+ assert.Equal(t, targets.version, storedVersion)
+ assert.Nil(t, err)
+ delete(persistedLocal, targets.name)
+ }
+ assert.Empty(t, persisted)
+ assert.Empty(t, persistedLocal)
+ })
+ }
+}
+
+func versionOfStoredTargets(name string, store map[string]json.RawMessage) (int64, error) {
+ rawTargets, ok := store[name]
+ if !ok {
+ return 0, nil
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(rawTargets, s); err != nil {
+ return 0, err
+ }
+ targets := &data.Targets{}
+ if err := json.Unmarshal(s.Signed, targets); err != nil {
+ return 0, err
+ }
+ return targets.Version, nil
+}
+
+func initTestDelegationClient(t *testing.T, dirPrefix string) (*Client, func() error) {
+ serverDir := dirPrefix + "/server"
+ initialStateDir := dirPrefix + "/client/metadata/current"
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ assert.Nil(t, err)
+ addr := l.Addr().String()
+ go http.Serve(l, http.FileServer(http.Dir(serverDir)))
+
+ opts := &HTTPRemoteOptions{
+ MetadataPath: "metadata",
+ TargetsPath: "targets",
+ }
+ remote, err := HTTPRemoteStore(fmt.Sprintf("http://%s/", addr), opts, nil)
+ assert.Nil(t, err)
+
+ c := NewClient(MemoryLocalStore(), remote)
+ rawFile, err := os.ReadFile(initialStateDir + "/" + "root.json")
+ assert.Nil(t, err)
+ assert.Nil(t, c.Init(rawFile))
+ files, err := os.ReadDir(initialStateDir)
+ assert.Nil(t, err)
+
+ // load local files
+ for _, f := range files {
+ if f.IsDir() {
+ continue
+ }
+ name := f.Name()
+ // ignoring consistent snapshot when loading initial state
+ if len(strings.Split(name, ".")) == 1 && strings.HasSuffix(name, ".json") {
+ rawFile, err := os.ReadFile(initialStateDir + "/" + name)
+ assert.Nil(t, err)
+ assert.Nil(t, c.local.SetMeta(name, rawFile))
+ }
+ }
+ return c, l.Close
+}
+
+func copyStore(store map[string]json.RawMessage) map[string]json.RawMessage {
+ new := make(map[string]json.RawMessage, len(store))
+ for k, raw := range store {
+ newRaw := make([]byte, len(raw))
+ copy(newRaw, []byte(raw))
+ new[k] = json.RawMessage(newRaw)
+ }
+ return new
+}
diff --git a/client/errors.go b/client/errors.go
new file mode 100644
index 0000000..3e7a5dc
--- /dev/null
+++ b/client/errors.go
@@ -0,0 +1,107 @@
+package client
+
+import (
+ "errors"
+ "fmt"
+)
+
+var (
+ ErrNoRootKeys = errors.New("tuf: no root keys found in local meta store")
+ ErrInsufficientKeys = errors.New("tuf: insufficient keys to meet threshold")
+ ErrNoLocalSnapshot = errors.New("tuf: no snapshot stored locally")
+)
+
+type ErrMissingRemoteMetadata struct {
+ Name string
+}
+
+func (e ErrMissingRemoteMetadata) Error() string {
+ return fmt.Sprintf("tuf: missing remote metadata %s", e.Name)
+}
+
+type ErrDownloadFailed struct {
+ File string
+ Err error
+}
+
+func (e ErrDownloadFailed) Error() string {
+ return fmt.Sprintf("tuf: failed to download %s: %s", e.File, e.Err)
+}
+
+type ErrDecodeFailed struct {
+ File string
+ Err error
+}
+
+func (e ErrDecodeFailed) Error() string {
+ return fmt.Sprintf("tuf: failed to decode %s: %s", e.File, e.Err)
+}
+
+type ErrMaxDelegations struct {
+ Target string
+ MaxDelegations int
+ SnapshotVersion int64
+}
+
+func (e ErrMaxDelegations) Error() string {
+ return fmt.Sprintf("tuf: max delegation of %d reached searching for %s with snapshot version %d", e.MaxDelegations, e.Target, e.SnapshotVersion)
+}
+
+type ErrNotFound struct {
+ File string
+}
+
+func (e ErrNotFound) Error() string {
+ return fmt.Sprintf("tuf: file not found: %s", e.File)
+}
+
+func IsNotFound(err error) bool {
+ _, ok := err.(ErrNotFound)
+ return ok
+}
+
+type ErrWrongSize struct {
+ File string
+ Actual int64
+ Expected int64
+}
+
+func (e ErrWrongSize) Error() string {
+ return fmt.Sprintf("tuf: unexpected file size: %s (expected %d bytes, got %d bytes)", e.File, e.Expected, e.Actual)
+}
+
+type ErrUnknownTarget struct {
+ Name string
+ SnapshotVersion int64
+}
+
+func (e ErrUnknownTarget) Error() string {
+ return fmt.Sprintf("tuf: unknown target file: %s with snapshot version %d", e.Name, e.SnapshotVersion)
+}
+
+type ErrMetaTooLarge struct {
+ Name string
+ Size int64
+ MaxSize int64
+}
+
+func (e ErrMetaTooLarge) Error() string {
+ return fmt.Sprintf("tuf: %s size %d bytes greater than maximum %d bytes", e.Name, e.Size, e.MaxSize)
+}
+
+type ErrInvalidURL struct {
+ URL string
+}
+
+func (e ErrInvalidURL) Error() string {
+ return fmt.Sprintf("tuf: invalid repository URL %s", e.URL)
+}
+
+type ErrRoleNotInSnapshot struct {
+ Role string
+ SnapshotVersion int64
+}
+
+func (e ErrRoleNotInSnapshot) Error() string {
+ return fmt.Sprintf("tuf: role %s not in snapshot version %d", e.Role, e.SnapshotVersion)
+}
diff --git a/client/file_store.go b/client/file_store.go
new file mode 100644
index 0000000..520bbe7
--- /dev/null
+++ b/client/file_store.go
@@ -0,0 +1,90 @@
+package client
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+)
+
+// FileRemoteStore provides a RemoteStore interface compatible
+// implementation that can be used where the RemoteStore is backed by a
+// fs.FS. This is useful for example in air-gapped environments where there's no
+// possibility to make outbound network connections.
+// By having this be a fs.FS instead of directories allows the repository to
+// be backed by something that's not persisted to disk.
+func NewFileRemoteStore(fsys fs.FS, targetDir string) (*FileRemoteStore, error) {
+ if fsys == nil {
+ return nil, errors.New("nil fs.FS")
+ }
+ t := targetDir
+ if t == "" {
+ t = "targets"
+ }
+ // Make sure directory exists
+ d, err := fsys.Open(t)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open targets directory %s: %w", t, err)
+ }
+ fi, err := d.Stat()
+ if err != nil {
+ return nil, fmt.Errorf("failed to stat targets directory %s: %w", t, err)
+ }
+ if !fi.IsDir() {
+ return nil, fmt.Errorf("targets directory not a directory %s", t)
+ }
+
+ fsysT, err := fs.Sub(fsys, t)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open targets directory %s: %w", t, err)
+ }
+ return &FileRemoteStore{fsys: fsys, targetDir: fsysT}, nil
+}
+
+type FileRemoteStore struct {
+ // Meta directory fs
+ fsys fs.FS
+ // Target directory fs.
+ targetDir fs.FS
+ // In order to be able to make write operations (create, delete) we can't
+ // use fs.FS for it (it's read only), so we have to know the underlying
+ // directory that add/delete test methods can use. This is only necessary
+ // for testing purposes.
+ testDir string
+}
+
+func (f *FileRemoteStore) GetMeta(name string) (io.ReadCloser, int64, error) {
+ rc, b, err := f.get(f.fsys, name)
+ return handleErrors(name, rc, b, err)
+}
+
+func (f *FileRemoteStore) GetTarget(name string) (io.ReadCloser, int64, error) {
+ rc, b, err := f.get(f.targetDir, name)
+ return handleErrors(name, rc, b, err)
+}
+
+func (f *FileRemoteStore) get(fsys fs.FS, s string) (io.ReadCloser, int64, error) {
+ if !fs.ValidPath(s) {
+ return nil, 0, fmt.Errorf("invalid path %s", s)
+ }
+
+ b, err := fs.ReadFile(fsys, s)
+ if err != nil {
+ return nil, -1, err
+ }
+ return io.NopCloser(bytes.NewReader(b)), int64(len(b)), nil
+}
+
+// handleErrors converts NotFound errors to something that TUF knows how to
+// handle properly. For example, when looking for n+1 root files, this is a
+// signal that it will stop looking.
+func handleErrors(name string, rc io.ReadCloser, b int64, err error) (io.ReadCloser, int64, error) {
+ if err == nil {
+ return rc, b, err
+ }
+ if errors.Is(err, fs.ErrNotExist) {
+ return rc, b, ErrNotFound{name}
+ }
+ return rc, b, err
+}
diff --git a/client/file_store_test.go b/client/file_store_test.go
new file mode 100644
index 0000000..95e02e3
--- /dev/null
+++ b/client/file_store_test.go
@@ -0,0 +1,197 @@
+package client
+
+import (
+ "bytes"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+const targetsDir = "targets"
+
+func TestCreates(t *testing.T) {
+ runningWindows := false
+ if runtime.GOOS == "windows" {
+ runningWindows = true
+ }
+ tmpDir := t.TempDir()
+ defer os.RemoveAll(tmpDir)
+ dir := filepath.Join(tmpDir, "repository")
+ os.Mkdir(dir, os.ModePerm)
+ os.Mkdir(filepath.Join(dir, "targets"), os.ModePerm)
+ if !runningWindows {
+ targetDirThatIsFile := filepath.Join(dir, "targets-that-isfile")
+ f, err := os.Create(targetDirThatIsFile)
+ if err != nil {
+ t.Fatalf("failed to create file: %s: %v", targetDirThatIsFile, err)
+ }
+ defer f.Close()
+ }
+ t.Cleanup(func() { rmrf(dir, t.Logf) })
+ t.Cleanup(func() { rmrf(tmpDir, t.Logf) })
+
+ tests := []struct {
+ name string
+ fsys fs.FS
+ td string
+ wantErr string
+ doNotRunOnWindows bool
+ }{{
+ name: "nil, error",
+ wantErr: "nil fs.FS",
+ }, {
+ name: "missing targets directory",
+ fsys: os.DirFS(dir),
+ td: "targets-not-there",
+ wantErr: "failed to open targets directory targets-not-there",
+ }, {
+ name: "targets directory is not a file",
+ fsys: os.DirFS(dir),
+ td: "targets-that-isfile",
+ wantErr: "targets directory not a directory targets-that-isfile",
+ doNotRunOnWindows: true,
+ }, {
+ name: "works, explicit targets",
+ fsys: os.DirFS(dir),
+ td: "targets",
+ }, {
+ name: "works, explicit targets",
+ fsys: os.DirFS(dir),
+ td: "targets",
+ }}
+
+ for _, tc := range tests {
+ if tc.doNotRunOnWindows {
+ t.Skip("Can't figure out how to make this work on windows")
+ }
+ _, err := NewFileRemoteStore(tc.fsys, tc.td)
+ if tc.wantErr != "" && err == nil {
+ t.Errorf("%q wanted error %s, got none", tc.name, tc.wantErr)
+ } else if tc.wantErr == "" && err != nil {
+ t.Errorf("%q did not want error, got: %v", tc.name, err)
+ } else if err != nil && !strings.Contains(err.Error(), tc.wantErr) {
+ t.Errorf("%q wanted error %s but got: %s", tc.name, tc.wantErr, err)
+ }
+ }
+}
+
+func TestBasicOps(t *testing.T) {
+ metas := map[string][]byte{
+ "root.json": []byte("root"),
+ "snapshot.json": []byte("snapshot"),
+ "timestamp": []byte("timestamp"),
+ }
+
+ fsys, dir, err := newTestFileStoreFS()
+ if err != nil {
+ t.Fatalf("Failed to create test FileStore")
+ }
+ t.Cleanup(func() { rmrf(dir, t.Logf) })
+ defer os.RemoveAll(dir)
+
+ // Add targets and metas and check them.
+ for k, v := range targetFiles {
+ if err := fsys.addTarget(k, v); err != nil {
+ t.Errorf("failed to add target %s: %v", k, err)
+ }
+ rc, size, err := fsys.GetTarget(k)
+ if err != nil {
+ t.Errorf("failed to GetTarget %s: %v", k, err)
+ }
+ if size != int64(len(v)) {
+ t.Errorf("unexpected size returned for GetTarget: %s want %d got %d", k, len(v), size)
+ }
+ got, err := io.ReadAll(rc)
+ if err != nil {
+ t.Errorf("failed to ReadAll returned ReacCloser %s: %v", k, err)
+ }
+ if !bytes.Equal(v, got) {
+ t.Errorf("Read unexpected bytes, want: %s got: %s", string(k), string(got))
+ }
+ }
+ for k, v := range metas {
+ if err := fsys.addMeta(k, v); err != nil {
+ t.Errorf("failed to add meta %s %v", k, err)
+ }
+ rc, size, err := fsys.GetMeta(k)
+ if err != nil {
+ t.Errorf("failed to GetMeta %s: %v", k, err)
+ }
+ if size != int64(len(v)) {
+ t.Errorf("unexpected size returned for GetMeta: %s want %d got %d", k, len(v), size)
+ }
+ got, err := io.ReadAll(rc)
+ if err != nil {
+ t.Errorf("failed to ReadAll returned ReacCloser %s: %v", k, err)
+ }
+ if !bytes.Equal(v, got) {
+ t.Errorf("Read unexpected bytes, want: %s got: %s", string(k), string(got))
+ }
+ }
+}
+
+// Test helper methods
+func (f *FileRemoteStore) addMeta(name string, data []byte) error {
+ return os.WriteFile(filepath.Join(f.testDir, name), data, os.ModePerm)
+}
+
+func (f *FileRemoteStore) addTarget(name string, data []byte) error {
+ fname := filepath.Join(f.testDir, targetsDir, name)
+ err := os.WriteFile(fname, data, os.ModePerm)
+ return err
+}
+
+func (f *FileRemoteStore) deleteMeta(name string) error {
+ return os.Remove(filepath.Join(f.testDir, name))
+}
+
+func (f *FileRemoteStore) deleteTarget(name string) error {
+ return os.Remove(filepath.Join(f.testDir, targetsDir, name))
+}
+
+func newTestFileStoreFS() (*FileRemoteStore, string, error) {
+ tmpDir := os.TempDir()
+ tufDir := filepath.Join(tmpDir, "tuf-file-store-test")
+ // Clean it in case there is cruft left around
+ os.RemoveAll(tufDir)
+ os.Mkdir(tufDir, os.ModePerm)
+ os.Mkdir(filepath.Join(tufDir, targetsDir), os.ModePerm)
+ fs, err := NewFileRemoteStore(os.DirFS(tufDir), targetsDir)
+ fs.testDir = tufDir
+ return fs, tufDir, err
+}
+
+// goes through a dir and removes everything. This is to work around:
+// https://github.com/golang/go/issues/51442
+func rmrf(dir string, logger func(string, ...interface{})) {
+ if dir == "" {
+ logger("cowardly refusing to remove a not fully specified fir")
+ return
+ }
+ logger("Removing %s", dir)
+ d, err := os.Open(dir)
+ if err != nil {
+ logger("Failed to open %s: %v", dir, err)
+ return
+ }
+ defer d.Close()
+ // -1 means give me everything, we don't have that many entries, so
+ // fine here.
+ names, err := d.Readdirnames(-1)
+ if err != nil {
+ logger("Failed to ReaddirNames %s: %v", dir, err)
+ return
+ }
+ for _, name := range names {
+ toRemove := filepath.Join(dir, name)
+ err = os.RemoveAll(toRemove)
+ if err != nil {
+ logger("Failed to RemoveAll %s: %v", toRemove, err)
+ // Do not want to fail here, just keep doing the best we can
+ }
+ }
+}
diff --git a/client/filejsonstore/filejsonstore.go b/client/filejsonstore/filejsonstore.go
new file mode 100644
index 0000000..41277c8
--- /dev/null
+++ b/client/filejsonstore/filejsonstore.go
@@ -0,0 +1,148 @@
+package client
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/theupdateframework/go-tuf/client"
+ "github.com/theupdateframework/go-tuf/internal/fsutil"
+ "github.com/theupdateframework/go-tuf/util"
+)
+
+const (
+ // user: rwx
+ // group: r-x
+ // other: ---
+ dirCreateMode = os.FileMode(0750)
+ // user: rw-
+ // group: r--
+ // other: ---
+ fileCreateMode = os.FileMode(0640)
+)
+
+// FileJSONStore represents a local metadata cache relying on raw JSON files
+// as retrieved from the remote repository.
+type FileJSONStore struct {
+ mtx sync.RWMutex
+ baseDir string
+}
+
+var _ client.LocalStore = (*FileJSONStore)(nil)
+
+// NewFileJSONStore returns a new metadata cache, implemented using raw JSON
+// files, stored in a directory provided by the client.
+// If the provided directory does not exist on disk, it will be created.
+// The provided metadata cache is safe for concurrent access.
+func NewFileJSONStore(baseDir string) (*FileJSONStore, error) {
+ f := &FileJSONStore{
+ baseDir: baseDir,
+ }
+
+ // Does the directory exist?
+ fi, err := os.Stat(baseDir)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ // Create the directory
+ if err = os.MkdirAll(baseDir, dirCreateMode); err != nil {
+ return nil, fmt.Errorf("error creating directory for metadata cache: %w", err)
+ }
+ } else {
+ return nil, fmt.Errorf("error getting FileInfo for %s: %w", baseDir, err)
+ }
+ } else {
+ // Verify that it is a directory
+ if !fi.IsDir() {
+ return nil, fmt.Errorf("can not open %s, not a directory", baseDir)
+ }
+ // Verify file mode is not too permissive.
+ if err = fsutil.EnsureMaxPermissions(fi, dirCreateMode); err != nil {
+ return nil, err
+ }
+ }
+
+ return f, nil
+}
+
+// GetMeta returns the currently cached set of metadata files.
+func (f *FileJSONStore) GetMeta() (map[string]json.RawMessage, error) {
+ f.mtx.RLock()
+ defer f.mtx.RUnlock()
+
+ names, err := os.ReadDir(f.baseDir)
+ if err != nil {
+ return nil, fmt.Errorf("error reading directory %s: %w", f.baseDir, err)
+ }
+
+ meta := map[string]json.RawMessage{}
+ for _, name := range names {
+ ok, err := fsutil.IsMetaFile(name)
+ if err != nil {
+ return nil, err
+ }
+ if !ok {
+ continue
+ }
+
+ // Verify permissions
+ info, err := name.Info()
+ if err != nil {
+ return nil, fmt.Errorf("error retrieving FileInfo for %s: %w", name.Name(), err)
+ }
+ if err = fsutil.EnsureMaxPermissions(info, fileCreateMode); err != nil {
+ return nil, err
+ }
+
+ p := filepath.Join(f.baseDir, name.Name())
+ b, err := os.ReadFile(p)
+ if err != nil {
+ return nil, fmt.Errorf("error reading file %s: %w", name.Name(), err)
+ }
+ meta[name.Name()] = b
+ }
+
+ return meta, nil
+}
+
+// SetMeta stores a metadata file in the cache. If the metadata file exist,
+// it will be overwritten.
+func (f *FileJSONStore) SetMeta(name string, meta json.RawMessage) error {
+ f.mtx.Lock()
+ defer f.mtx.Unlock()
+
+ if filepath.Ext(name) != ".json" {
+ return fmt.Errorf("file %s is not a JSON file", name)
+ }
+
+ p := filepath.Join(f.baseDir, name)
+ err := util.AtomicallyWriteFile(p, meta, fileCreateMode)
+ return err
+}
+
+// DeleteMeta deletes a metadata file from the cache.
+// If the file does not exist, an *os.PathError is returned.
+func (f *FileJSONStore) DeleteMeta(name string) error {
+ f.mtx.Lock()
+ defer f.mtx.Unlock()
+
+ if filepath.Ext(name) != ".json" {
+ return fmt.Errorf("file %s is not a JSON file", name)
+ }
+
+ p := filepath.Join(f.baseDir, name)
+ err := os.Remove(p)
+ if err == nil {
+ return nil
+ }
+
+ return fmt.Errorf("error deleting file %s: %w", name, err)
+}
+
+// Close closes the metadata cache. This is a no-op.
+func (f *FileJSONStore) Close() error {
+ return nil
+}
diff --git a/client/filejsonstore/filejsonstore_test.go b/client/filejsonstore/filejsonstore_test.go
new file mode 100644
index 0000000..6d00e04
--- /dev/null
+++ b/client/filejsonstore/filejsonstore_test.go
@@ -0,0 +1,194 @@
+package client
+
+import (
+ "encoding/json"
+ "errors"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "gopkg.in/check.v1"
+)
+
+type FileJSONStoreSuite struct{}
+
+var _ = check.Suite(&FileJSONStoreSuite{})
+
+// Hook up gocheck into the "go test" runner
+func Test(t *testing.T) { check.TestingT(t) }
+
+func (FileJSONStoreSuite) TestNewOk(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+
+ // Assert path does not exist
+ fi, err := os.Stat(p)
+ c.Assert(fi, check.IsNil)
+ c.Assert(errors.Is(err, os.ErrNotExist), check.Equals, true)
+
+ // Create implementation
+ s, err := NewFileJSONStore(p)
+ c.Assert(err, check.IsNil)
+ c.Assert(s, check.NotNil)
+
+ // Assert path does exist and is a directory
+ fi, err = os.Stat(p)
+ c.Assert(fi, check.NotNil)
+ c.Assert(err, check.IsNil)
+ c.Assert(fi.IsDir(), check.Equals, true)
+}
+
+func (FileJSONStoreSuite) TestNewFileExists(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+
+ // Create an empty file
+ f, err := os.Create(p)
+ c.Assert(err, check.IsNil)
+ f.Close()
+
+ // Create implementation
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.IsNil)
+ c.Assert(err, check.NotNil)
+ found := strings.Contains(err.Error(), ", not a directory")
+ c.Assert(found, check.Equals, true)
+}
+
+func (FileJSONStoreSuite) TestNewDirectoryExists(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+
+ err := os.Mkdir(p, 0750)
+ c.Assert(err, check.IsNil)
+
+ // Create implementation
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.NotNil)
+ c.Assert(err, check.IsNil)
+}
+
+func (FileJSONStoreSuite) TestGetMetaEmpty(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+ s, err := NewFileJSONStore(p)
+ c.Assert(err, check.IsNil)
+
+ md, err := s.GetMeta()
+ c.Assert(err, check.IsNil)
+ c.Assert(md, check.HasLen, 0)
+}
+
+func (FileJSONStoreSuite) TestGetNoDirectory(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+ s, err := NewFileJSONStore(p)
+ c.Assert(err, check.IsNil)
+
+ err = os.Remove(p)
+ c.Assert(err, check.IsNil)
+
+ md, err := s.GetMeta()
+ c.Assert(md, check.IsNil)
+ c.Assert(err, check.NotNil)
+}
+
+func (FileJSONStoreSuite) TestMetadataOperations(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+ s, err := NewFileJSONStore(p)
+ c.Assert(err, check.IsNil)
+
+ expected := map[string]json.RawMessage{
+ "file1.json": []byte{0xf1, 0xe1, 0xd1},
+ "file2.json": []byte{0xf2, 0xe2, 0xd2},
+ "file3.json": []byte{0xf3, 0xe3, 0xd3},
+ }
+
+ for k, v := range expected {
+ err := s.SetMeta(k, v)
+ c.Assert(err, check.IsNil)
+ }
+
+ md, err := s.GetMeta()
+ c.Assert(err, check.IsNil)
+ c.Assert(md, check.HasLen, 3)
+ c.Assert(md, check.DeepEquals, expected)
+
+ // Delete all items
+ count := 3
+ for k := range expected {
+ err = s.DeleteMeta(k)
+ c.Assert(err, check.IsNil)
+
+ md, err := s.GetMeta()
+ c.Assert(err, check.IsNil)
+
+ count--
+ c.Assert(md, check.HasLen, count)
+ }
+
+ md, err = s.GetMeta()
+ c.Assert(err, check.IsNil)
+ c.Assert(md, check.HasLen, 0)
+}
+
+func (FileJSONStoreSuite) TestGetNoJSON(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.NotNil)
+ c.Assert(err, check.IsNil)
+
+ // Create a file which does not end with '.json'
+ fp := filepath.Join(p, "meta.xml")
+ err = os.WriteFile(fp, []byte{}, 0644)
+ c.Assert(err, check.IsNil)
+
+ md, err := s.GetMeta()
+ c.Assert(err, check.IsNil)
+ c.Assert(md, check.HasLen, 0)
+}
+
+func (FileJSONStoreSuite) TestNoJSON(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.NotNil)
+ c.Assert(err, check.IsNil)
+
+ files := []string{
+ "file.xml",
+ "file",
+ "",
+ }
+ for _, f := range files {
+ err := s.SetMeta(f, []byte{})
+ c.Assert(err, check.ErrorMatches, "file.*is not a JSON file")
+ }
+}
+
+func (FileJSONStoreSuite) TestClose(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.NotNil)
+ c.Assert(err, check.IsNil)
+
+ err = s.Close()
+ c.Assert(err, check.IsNil)
+}
+
+func (FileJSONStoreSuite) TestDelete(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.NotNil)
+ c.Assert(err, check.IsNil)
+
+ err = s.DeleteMeta("not_json.yml")
+ c.Assert(err, check.ErrorMatches, "file not_json\\.yml is not a JSON file")
+ err = s.DeleteMeta("non_existing.json")
+ c.Assert(errors.Is(err, os.ErrNotExist), check.Equals, true)
+}
diff --git a/client/filejsonstore/perm_test.go b/client/filejsonstore/perm_test.go
new file mode 100644
index 0000000..cda58c4
--- /dev/null
+++ b/client/filejsonstore/perm_test.go
@@ -0,0 +1,54 @@
+//go:build !windows
+// +build !windows
+
+package client
+
+import (
+ "os"
+ "path/filepath"
+
+ "gopkg.in/check.v1"
+)
+
+func (FileJSONStoreSuite) TestNewDirectoryExistsWrongPerm(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+
+ err := os.Mkdir(p, 0750)
+ c.Assert(err, check.IsNil)
+
+ // Modify the directory permission and try again
+ err = os.Chmod(p, 0751)
+ c.Assert(err, check.IsNil)
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.IsNil)
+ c.Assert(err, check.ErrorMatches, "permission bits for file tuf_raw.db failed.*")
+}
+
+func (FileJSONStoreSuite) TestNewNoCreate(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+
+ // Clear the write bit for the user
+ err := os.Chmod(tmp, 0551)
+ c.Assert(err, check.IsNil)
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.IsNil)
+ c.Assert(err, check.NotNil)
+}
+
+func (FileJSONStoreSuite) TestGetTooPermissive(c *check.C) {
+ tmp := c.MkDir()
+ p := filepath.Join(tmp, "tuf_raw.db")
+ s, err := NewFileJSONStore(p)
+ c.Assert(s, check.NotNil)
+ c.Assert(err, check.IsNil)
+
+ fp := filepath.Join(p, "meta.json")
+ err = os.WriteFile(fp, []byte{}, 0644)
+ c.Assert(err, check.IsNil)
+
+ md, err := s.GetMeta()
+ c.Assert(md, check.IsNil)
+ c.Assert(err, check.ErrorMatches, "permission bits for file meta.json failed.*")
+}
diff --git a/client/interop_test.go b/client/interop_test.go
new file mode 100644
index 0000000..1fb4f98
--- /dev/null
+++ b/client/interop_test.go
@@ -0,0 +1,201 @@
+package client
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+
+ "github.com/theupdateframework/go-tuf/util"
+ . "gopkg.in/check.v1"
+
+ goTufGenerator "github.com/theupdateframework/go-tuf/client/testdata/go-tuf/generator"
+)
+
+type InteropSuite struct{}
+
+var _ = Suite(&InteropSuite{})
+
+func (InteropSuite) TestGoClientIdentityConsistentSnapshotFalse(c *C) {
+ checkGoIdentity(c, false)
+}
+
+func (InteropSuite) TestGoClientIdentityConsistentSnapshotTrue(c *C) {
+ checkGoIdentity(c, true)
+}
+
+func checkGoIdentity(c *C, consistentSnapshot bool) {
+ cwd, err := os.Getwd()
+ c.Assert(err, IsNil)
+ testDataDir := filepath.Join(cwd, "testdata")
+
+ tempDir, err := os.MkdirTemp("", "")
+ c.Assert(err, IsNil)
+ defer os.RemoveAll(tempDir)
+
+ // Generate the metadata and compute hashes for all the files.
+ goTufGenerator.Generate(tempDir, filepath.Join(testDataDir, "keys.json"), consistentSnapshot)
+ hashes := computeHashes(c, tempDir)
+
+ snapshotDir := filepath.Join(testDataDir, "go-tuf", fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot))
+ snapshotHashes := computeHashes(c, snapshotDir)
+
+ c.Assert(hashes, DeepEquals, snapshotHashes, Commentf("metadata out of date, regenerate by running client/testdata/go-tuf/regenerate-metadata.sh"))
+}
+
+func computeHashes(c *C, dir string) map[string]string {
+ hashes := make(map[string]string)
+
+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if info.IsDir() {
+ return nil
+ }
+
+ bytes, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ path, err = filepath.Rel(dir, path)
+ if err != nil {
+ return err
+ }
+ hashes[path] = string(bytes)
+
+ return nil
+ })
+ c.Assert(err, IsNil)
+
+ return hashes
+}
+
+func (InteropSuite) TestGoClientCompatibility(c *C) {
+ names := []string{
+ "go-tuf",
+ "go-tuf-transition-M3",
+ "go-tuf-transition-M4",
+ }
+ options := &HTTPRemoteOptions{MetadataPath: "", TargetsPath: "targets"}
+
+ for _, name := range names {
+ for _, consistentSnapshot := range []bool{false, true} {
+ t := newTestCase(c, name, consistentSnapshot, options)
+ t.run(c)
+ }
+ }
+}
+
+type testCase struct {
+ name string
+ consistentSnapshot bool
+ options *HTTPRemoteOptions
+ local LocalStore
+ targets map[string][]byte
+ testDir string
+ testSteps []string
+}
+
+func newTestCase(c *C, name string, consistentSnapshot bool, options *HTTPRemoteOptions) testCase {
+ cwd, err := os.Getwd()
+ c.Assert(err, IsNil)
+ testDir := filepath.Join(cwd, "testdata", name, fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot))
+
+ dirEntries, err := os.ReadDir(testDir)
+ c.Assert(err, IsNil)
+ c.Assert(dirEntries, Not(HasLen), 0)
+
+ testSteps := []string{}
+ for _, dirEntry := range dirEntries {
+ if dirEntry.IsDir() {
+ testSteps = append(testSteps, dirEntry.Name())
+ }
+ }
+
+ return testCase{
+ name: name,
+ consistentSnapshot: consistentSnapshot,
+ options: options,
+ local: MemoryLocalStore(),
+ targets: make(map[string][]byte),
+ testDir: testDir,
+ testSteps: testSteps,
+ }
+}
+
+func (t *testCase) run(c *C) {
+ c.Logf("test case: %s consistent-snapshot: %t", t.name, t.consistentSnapshot)
+
+ for _, stepName := range t.testSteps {
+ t.runStep(c, stepName)
+ }
+}
+
+func (t *testCase) runStep(c *C, stepName string) {
+ c.Logf("step: %s", stepName)
+
+ addr, cleanup := startFileServer(c, t.testDir)
+ defer cleanup()
+
+ remote, err := HTTPRemoteStore(fmt.Sprintf("http://%s/%s/repository", addr, stepName), t.options, nil)
+ c.Assert(err, IsNil)
+
+ client := NewClient(t.local, remote)
+ // 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.Init(rootJsonBytes), IsNil)
+
+ // check update returns the correct updated targets
+ files, err := client.Update()
+ c.Assert(err, IsNil)
+ if stepName != "2" {
+ // The rest of the test cases add one target file at a time for each cycle, so this is why we expect that
+ // the number of updated targets returned by Update() should equals to 1
+ c.Assert(files, HasLen, 1)
+ } else {
+ // The following test case (#2) verifies that when a targets key has been rotated in the latest 3.root.json,
+ // the local targets.json meta is indeed ignored since it's signed with a key that has been now changed.
+ // The reason we check for 3 here is that the updated targets corresponds to all target files listed in the
+ // targets.json for test case #2
+ c.Assert(files, HasLen, 3)
+ }
+ targetName := stepName
+ t.targets[targetName] = []byte(targetName)
+
+ file, ok := files[targetName]
+ if !ok {
+ c.Fatalf("expected updated targets to contain %s", targetName)
+ }
+
+ data := t.targets[targetName]
+ meta, err := util.GenerateTargetFileMeta(bytes.NewReader(data), file.HashAlgorithms()...)
+ c.Assert(err, IsNil)
+ c.Assert(util.TargetFileMetaEqual(file, meta), IsNil)
+
+ // download the files and check they have the correct content
+ for name, data := range t.targets {
+ for _, prefix := range []string{"", "/"} {
+ var dest testDestination
+ c.Assert(client.Download(prefix+name, &dest), IsNil)
+ c.Assert(dest.deleted, Equals, false)
+ c.Assert(dest.String(), Equals, string(data))
+ }
+ }
+}
+
+func startFileServer(c *C, dir string) (string, func() error) {
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ c.Assert(err, IsNil)
+ addr := l.Addr().String()
+ go http.Serve(l, http.FileServer(http.Dir(dir)))
+ return addr, l.Close
+}
diff --git a/client/leveldbstore/leveldbstore.go b/client/leveldbstore/leveldbstore.go
new file mode 100644
index 0000000..d939049
--- /dev/null
+++ b/client/leveldbstore/leveldbstore.go
@@ -0,0 +1,60 @@
+package client
+
+import (
+ "encoding/json"
+
+ "github.com/syndtr/goleveldb/leveldb"
+ "github.com/syndtr/goleveldb/leveldb/errors"
+ "github.com/syndtr/goleveldb/leveldb/storage"
+
+ tuf_client "github.com/theupdateframework/go-tuf/client"
+)
+
+func FileLocalStore(path string) (tuf_client.LocalStore, error) {
+ fd, err := storage.OpenFile(path, false)
+ if err != nil {
+ return nil, err
+ }
+
+ db, err := leveldb.Open(fd, nil)
+ if err != nil && errors.IsCorrupted(err) {
+ db, err = leveldb.Recover(fd, nil)
+ }
+
+ return &fileLocalStore{fd: fd, db: db}, err
+}
+
+type fileLocalStore struct {
+ fd storage.Storage
+ db *leveldb.DB
+}
+
+func (f *fileLocalStore) GetMeta() (map[string]json.RawMessage, error) {
+ meta := make(map[string]json.RawMessage)
+ db_itr := f.db.NewIterator(nil, nil)
+ for db_itr.Next() {
+ vcopy := make([]byte, len(db_itr.Value()))
+ copy(vcopy, db_itr.Value())
+ meta[string(db_itr.Key())] = vcopy
+ }
+ db_itr.Release()
+ return meta, db_itr.Error()
+}
+
+func (f *fileLocalStore) SetMeta(name string, meta json.RawMessage) error {
+ return f.db.Put([]byte(name), []byte(meta), nil)
+}
+
+func (f *fileLocalStore) DeleteMeta(name string) error {
+ return f.db.Delete([]byte(name), nil)
+}
+
+func (f *fileLocalStore) Close() error {
+ // Always close both before returning any errors
+ dbCloseErr := f.db.Close()
+ fdCloseErr := f.fd.Close()
+ if dbCloseErr != nil {
+ return dbCloseErr
+ }
+ return fdCloseErr
+}
diff --git a/client/leveldbstore/leveldbstore_test.go b/client/leveldbstore/leveldbstore_test.go
new file mode 100644
index 0000000..5d29fff
--- /dev/null
+++ b/client/leveldbstore/leveldbstore_test.go
@@ -0,0 +1,123 @@
+package client
+
+import (
+ "encoding/json"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+ "os"
+)
+
+type LocalStoreSuite struct{}
+
+var _ = Suite(&LocalStoreSuite{})
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+func (LocalStoreSuite) TestFileLocalStore(c *C) {
+ tmp := c.MkDir()
+ path := filepath.Join(tmp, "tuf.db")
+ store, err := FileLocalStore(path)
+ c.Assert(err, IsNil)
+ defer store.Close()
+
+ type meta map[string]json.RawMessage
+
+ assertGet := func(expected meta) {
+ actual, err := store.GetMeta()
+ c.Assert(err, IsNil)
+ c.Assert(meta(actual), DeepEquals, expected)
+ }
+
+ // initial GetMeta should return empty meta
+ assertGet(meta{})
+
+ // SetMeta should persist
+ rootJSON := []byte(`{"_type":"Root"}`)
+ c.Assert(store.SetMeta("root.json", rootJSON), IsNil)
+ assertGet(meta{"root.json": rootJSON})
+
+ // SetMeta should add to existing meta
+ targetsJSON := []byte(`{"_type":"Target"}`)
+ c.Assert(store.SetMeta("targets.json", targetsJSON), IsNil)
+ assertGet(meta{"root.json": rootJSON, "targets.json": targetsJSON})
+
+ // a new store should get the same meta
+ c.Assert(store.Close(), IsNil)
+ store, err = FileLocalStore(path)
+ c.Assert(err, IsNil)
+ defer func() {
+ c.Assert(store.Close(), IsNil)
+ }()
+ assertGet(meta{"root.json": rootJSON, "targets.json": targetsJSON})
+}
+
+func (LocalStoreSuite) TestDeleteMeta(c *C) {
+ tmp := c.MkDir()
+ path := filepath.Join(tmp, "tuf.db")
+ store, err := FileLocalStore(path)
+ c.Assert(err, IsNil)
+
+ type meta map[string]json.RawMessage
+
+ assertGet := func(expected meta) {
+ actual, err := store.GetMeta()
+ c.Assert(err, IsNil)
+ c.Assert(meta(actual), DeepEquals, expected)
+ }
+
+ // initial GetMeta should return empty meta
+ assertGet(meta{})
+
+ // SetMeta should persist
+ rootJSON := []byte(`{"_type":"Root"}`)
+ c.Assert(store.SetMeta("root.json", rootJSON), IsNil)
+ assertGet(meta{"root.json": rootJSON})
+
+ store.DeleteMeta("root.json")
+ m, _ := store.GetMeta()
+ if _, ok := m["root.json"]; ok {
+ c.Fatalf("Metadata is not deleted!")
+ }
+}
+
+func (LocalStoreSuite) TestCorruptManifest(c *C) {
+ tmp := c.MkDir()
+ path := filepath.Join(tmp, "tuf.db")
+
+ store, err := FileLocalStore(path)
+ c.Assert(err, IsNil)
+
+ // now break the manifest file
+ err = os.Truncate(filepath.Join(path, "MANIFEST-000000"), 1)
+ c.Assert(err, IsNil)
+ err = store.Close()
+ c.Assert(err, IsNil)
+
+ store, err = FileLocalStore(path)
+ c.Assert(err, IsNil)
+
+ type meta map[string]json.RawMessage
+
+ assertGet := func(expected meta) {
+ actual, err := store.GetMeta()
+ c.Assert(err, IsNil)
+ c.Assert(meta(actual), DeepEquals, expected)
+ }
+
+ // initial GetMeta should return empty meta
+ assertGet(meta{})
+
+ // SetMeta should persist
+ rootJSON := []byte(`{"_type":"Root"}`)
+ c.Assert(store.SetMeta("root.json", rootJSON), IsNil)
+ assertGet(meta{"root.json": rootJSON})
+
+ store.DeleteMeta("root.json")
+ m, _ := store.GetMeta()
+ if _, ok := m["root.json"]; ok {
+ c.Fatalf("Metadata is not deleted!")
+ }
+}
diff --git a/client/local_store.go b/client/local_store.go
new file mode 100644
index 0000000..bb9421f
--- /dev/null
+++ b/client/local_store.go
@@ -0,0 +1,29 @@
+package client
+
+import (
+ "encoding/json"
+)
+
+func MemoryLocalStore() LocalStore {
+ return make(memoryLocalStore)
+}
+
+type memoryLocalStore map[string]json.RawMessage
+
+func (m memoryLocalStore) GetMeta() (map[string]json.RawMessage, error) {
+ return m, nil
+}
+
+func (m memoryLocalStore) SetMeta(name string, meta json.RawMessage) error {
+ m[name] = meta
+ return nil
+}
+
+func (m memoryLocalStore) DeleteMeta(name string) error {
+ delete(m, name)
+ return nil
+}
+
+func (m memoryLocalStore) Close() error {
+ return nil
+}
diff --git a/client/local_store_test.go b/client/local_store_test.go
new file mode 100644
index 0000000..e2c1dfd
--- /dev/null
+++ b/client/local_store_test.go
@@ -0,0 +1,29 @@
+package client
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDeleteMeta(t *testing.T) {
+ l := MemoryLocalStore()
+ defer func() {
+ assert.Equal(t, nil, l.Close())
+ }()
+ assert.Equal(t, l.SetMeta("root.json", []byte(`
+ {
+ "signed": {},
+ "signatures": {},
+ }
+ `)), nil)
+ m, err := l.GetMeta()
+ assert.Equal(t, err, nil)
+ if _, ok := m["root.json"]; !ok {
+ t.Fatalf("Expected metadata not found!")
+ }
+ l.DeleteMeta("root.json")
+ if _, ok := m["root.json"]; ok {
+ t.Fatalf("Metadata is not deleted!")
+ }
+}
diff --git a/client/python_interop/python_interop_test.go b/client/python_interop/python_interop_test.go
new file mode 100644
index 0000000..d29e0ac
--- /dev/null
+++ b/client/python_interop/python_interop_test.go
@@ -0,0 +1,236 @@
+package client
+
+import (
+ "bytes"
+ "fmt"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ tuf "github.com/theupdateframework/go-tuf"
+ client "github.com/theupdateframework/go-tuf/client"
+ "github.com/theupdateframework/go-tuf/util"
+ . "gopkg.in/check.v1"
+)
+
+type InteropSuite struct{}
+
+var _ = Suite(&InteropSuite{})
+
+var pythonTargets = map[string][]byte{
+ "file1.txt": []byte("file1.txt"),
+ "dir/file2.txt": []byte("file2.txt"),
+}
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type testDestination struct {
+ bytes.Buffer
+ deleted bool
+}
+
+func (t *testDestination) Delete() error {
+ t.deleted = true
+ return nil
+}
+
+func (InteropSuite) TestGoClientPythonGenerated(c *C) {
+ // start file server
+ cwd, err := os.Getwd()
+ c.Assert(err, IsNil)
+ testDataDir := filepath.Join(cwd, "testdata", "python-tuf-v3.0.0")
+ addr, cleanup := startFileServer(c, testDataDir)
+ defer cleanup()
+
+ for _, dir := range []string{"without-consistent-snapshot", "with-consistent-snapshot"} {
+ remote, err := client.HTTPRemoteStore(
+ fmt.Sprintf("http://%s/%s/repository", addr, dir),
+ &client.HTTPRemoteOptions{MetadataPath: "metadata", TargetsPath: "targets"},
+ nil,
+ )
+ c.Assert(err, IsNil)
+
+ // initiate a client with the root metadata
+ client := client.NewClient(client.MemoryLocalStore(), remote)
+ rootJSON, err := os.ReadFile(filepath.Join(testDataDir, dir, "repository", "metadata", "1.root.json"))
+ c.Assert(err, IsNil)
+ c.Assert(client.Init(rootJSON), IsNil)
+
+ // check update returns the correct updated targets
+ files, err := client.Update()
+ c.Assert(err, IsNil)
+ c.Assert(files, HasLen, len(pythonTargets))
+ for name, data := range pythonTargets {
+ file, ok := files[name]
+ if !ok {
+ c.Fatalf("expected updated targets to contain %s", name)
+ }
+ meta, err := util.GenerateTargetFileMeta(bytes.NewReader(data), file.HashAlgorithms()...)
+ c.Assert(err, IsNil)
+ c.Assert(util.TargetFileMetaEqual(file, meta), IsNil)
+ }
+
+ // download the files and check they have the correct content
+ for name, data := range pythonTargets {
+ var dest testDestination
+ c.Assert(client.Download(name, &dest), IsNil)
+ c.Assert(dest.deleted, Equals, false)
+ c.Assert(dest.String(), Equals, string(data))
+ }
+ }
+}
+
+func generateRepoFS(c *C, dir string, files map[string][]byte,
+ consistentSnapshot bool) *tuf.Repo {
+ repo, err := tuf.NewRepo(tuf.FileSystemStore(dir, nil))
+ c.Assert(err, IsNil)
+ if !consistentSnapshot {
+ c.Assert(repo.Init(false), IsNil)
+ }
+ for _, role := range []string{"root", "snapshot", "targets", "timestamp"} {
+ _, err := repo.GenKey(role)
+ c.Assert(err, IsNil)
+ }
+ for file, data := range files {
+ path := filepath.Join(dir, "staged", "targets", file)
+ c.Assert(os.MkdirAll(filepath.Dir(path), 0755), IsNil)
+ c.Assert(os.WriteFile(path, data, 0644), IsNil)
+ c.Assert(repo.AddTarget(file, nil), IsNil)
+ }
+ c.Assert(repo.Snapshot(), IsNil)
+ c.Assert(repo.Timestamp(), IsNil)
+ c.Assert(repo.Commit(), IsNil)
+ return repo
+}
+
+func refreshRepo(c *C, repo *tuf.Repo) {
+ c.Assert(repo.Snapshot(), IsNil)
+ c.Assert(repo.Timestamp(), IsNil)
+ c.Assert(repo.Commit(), IsNil)
+}
+
+func (InteropSuite) TestPythonClientGoGenerated(c *C) {
+ // clone the Python client if necessary
+ cwd, err := os.Getwd()
+ c.Assert(err, IsNil)
+
+ files := map[string][]byte{
+ "foo.txt": []byte("foo"),
+ "bar/baz.txt": []byte("baz"),
+ }
+
+ for _, consistentSnapshot := range []bool{false, true} {
+ // generate repository
+ tmp := c.MkDir()
+ // start file server
+ addr, cleanup := startFileServer(c, tmp)
+ defer cleanup()
+ name := fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot)
+ dir := filepath.Join(tmp, name)
+ generateRepoFS(c, dir, files, consistentSnapshot)
+
+ // create initial files for Python client
+ clientDir := filepath.Join(dir, "client")
+ currDir := filepath.Join(clientDir, "tufrepo", "metadata", "current")
+ prevDir := filepath.Join(clientDir, "tufrepo", "metadata", "previous")
+ c.Assert(os.MkdirAll(currDir, 0755), IsNil)
+ c.Assert(os.MkdirAll(prevDir, 0755), IsNil)
+ rootJSON, err := os.ReadFile(filepath.Join(dir, "repository", "1.root.json"))
+ c.Assert(err, IsNil)
+ c.Assert(os.WriteFile(filepath.Join(currDir, "root.json"), rootJSON, 0644), IsNil)
+
+ args := []string{
+ filepath.Join(cwd, "testdata", "python-tuf-v3.0.0", "client.py"),
+ "--repo=http://" + addr + "/" + name,
+ }
+ for path := range files {
+ args = append(args, path)
+ }
+
+ // run Python client update
+ cmd := exec.Command("python", args...)
+ cmd.Dir = clientDir
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ c.Assert(cmd.Run(), IsNil)
+
+ // check the target files got downloaded
+ for path, expected := range files {
+ actual, err := os.ReadFile(filepath.Join(clientDir, "tuftargets", url.QueryEscape(path)))
+ c.Assert(err, IsNil)
+ c.Assert(actual, DeepEquals, expected)
+ }
+ }
+}
+
+// This is a regression test for issue
+// https://github.com/theupdateframework/go-tuf/issues/402
+func (InteropSuite) TestPythonClientGoGeneratedNullDelegations(c *C) {
+ // clone the Python client if necessary
+ cwd, err := os.Getwd()
+ c.Assert(err, IsNil)
+
+ files := map[string][]byte{
+ "foo.txt": []byte("foo"),
+ "bar/baz.txt": []byte("baz"),
+ }
+
+ for _, consistentSnapshot := range []bool{false, true} {
+ // generate repository
+ tmp := c.MkDir()
+ // start file server
+ addr, cleanup := startFileServer(c, tmp)
+ defer cleanup()
+ name := fmt.Sprintf("consistent-snapshot-delegations-%t", consistentSnapshot)
+ dir := filepath.Join(tmp, name)
+ repo := generateRepoFS(c, dir, files, consistentSnapshot)
+ // "Reset" top-level targets delegations and re-sign
+ c.Assert(repo.ResetTargetsDelegations("targets"), IsNil)
+ refreshRepo(c, repo)
+
+ // create initial files for Python client
+ clientDir := filepath.Join(dir, "client")
+ currDir := filepath.Join(clientDir, "tufrepo", "metadata", "current")
+ prevDir := filepath.Join(clientDir, "tufrepo", "metadata", "previous")
+ c.Assert(os.MkdirAll(currDir, 0755), IsNil)
+ c.Assert(os.MkdirAll(prevDir, 0755), IsNil)
+ rootJSON, err := os.ReadFile(filepath.Join(dir, "repository", "1.root.json"))
+ c.Assert(err, IsNil)
+ c.Assert(os.WriteFile(filepath.Join(currDir, "root.json"), rootJSON, 0644), IsNil)
+
+ args := []string{
+ filepath.Join(cwd, "testdata", "python-tuf-v3.0.0", "client.py"),
+ "--repo=http://" + addr + "/" + name,
+ }
+ for path := range files {
+ args = append(args, path)
+ }
+
+ // run Python client update
+ cmd := exec.Command("python", args...)
+ cmd.Dir = clientDir
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ c.Assert(cmd.Run(), IsNil)
+
+ // check the target files got downloaded
+ for path, expected := range files {
+ actual, err := os.ReadFile(filepath.Join(clientDir, "tuftargets", url.QueryEscape(path)))
+ c.Assert(err, IsNil)
+ c.Assert(actual, DeepEquals, expected)
+ }
+ }
+}
+
+func startFileServer(c *C, dir string) (string, func() error) {
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ c.Assert(err, IsNil)
+ addr := l.Addr().String()
+ go http.Serve(l, http.FileServer(http.Dir(dir)))
+ return addr, l.Close
+}
diff --git a/client/python_interop/testdata/LICENSE.txt b/client/python_interop/testdata/LICENSE.txt
new file mode 100644
index 0000000..544f53d
--- /dev/null
+++ b/client/python_interop/testdata/LICENSE.txt
@@ -0,0 +1,66 @@
+ This file contains the license for TUF: The Update Framework.
+
+ It also lists license information for components and source
+ code used by TUF: The Update Framework.
+
+ If you got this file as a part of a larger bundle,
+ there may be other license terms that you should be aware of.
+
+===============================================================================
+TUF: The Update Framework is distributed under this license:
+
+Copyright (c) 2010, Justin Samuel and Justin Cappos.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and/or hardware specification (the “Work”) to deal in the Work
+without restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies of the Work,
+and to permit persons to whom the Work is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Work.
+
+THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER
+DEALINGS IN THE WORK.
+===============================================================================
+Many files are modified from Thandy and are licensed under the
+following license:
+
+Thandy is distributed under this license:
+
+Copyright (c) 2008, The Tor Project, Inc.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+ * Neither the names of the copyright owners nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+===============================================================================
diff --git a/client/python_interop/testdata/Makefile b/client/python_interop/testdata/Makefile
new file mode 100644
index 0000000..6ba9a64
--- /dev/null
+++ b/client/python_interop/testdata/Makefile
@@ -0,0 +1,10 @@
+PYTHON_TUF=python-tuf-v3.0.0
+
+all:
+ docker build -t tuf-gen ./$(PYTHON_TUF)
+ docker run tuf-gen | tar x -C $(PYTHON_TUF)
+
+clean:
+ rm -rf $(PYTHON_TUF)/with{,out}-consistent-snapshot
+
+.PHONY: all clean
diff --git a/client/python_interop/testdata/README.md b/client/python_interop/testdata/README.md
new file mode 100644
index 0000000..011b998
--- /dev/null
+++ b/client/python_interop/testdata/README.md
@@ -0,0 +1,47 @@
+# TUF testdata
+
+TUF testdata generated by the Python implementation which is used to test that
+the Go client is compatible with files generated by the Python repository tool.
+
+## Generate
+
+The `generate` directory contains scripts and a Dockerfile for generating the
+test data files.
+
+Run `make` to regenerate the test files:
+
+```
+$ make clean
+rm -rf keystore repository
+
+$ make
+docker build -t tuf-gen ./generate
+...
+Successfully built ac1fba1d0b3b
+docker run tuf-gen | tar x
+Files generated:
+.
+|-- keystore
+| |-- root_key
+| |-- root_key.pub
+| |-- snapshot_key
+| |-- snapshot_key.pub
+| |-- targets_key
+| |-- targets_key.pub
+| |-- timestamp_key
+| `-- timestamp_key.pub
+|-- repository
+| |-- metadata
+| | |-- root.json
+| | |-- snapshot.json
+| | |-- targets.json
+| | |-- targets.json.gz
+| | `-- timestamp.json
+| `-- targets
+| |-- dir
+| | `-- file2.txt
+| `-- file1.txt
+`-- tuf.log
+
+5 directories, 16 files
+```
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/Dockerfile b/client/python_interop/testdata/python-tuf-v3.0.0/Dockerfile
new file mode 100644
index 0000000..896820c
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+
+RUN apt-get update && apt-get install -y libsodium-dev tree
+RUN pip install -U pip && pip install 'securesystemslib[crypto,pynacl]==0.28.0' 'tuf==v3.0.0'
+
+ADD generate.py generate.sh /
+CMD /generate.sh
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/client.py b/client/python_interop/testdata/python-tuf-v3.0.0/client.py
new file mode 100644
index 0000000..64fe539
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/client.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+
+# Copyright 2012 - 2023, New York University and the TUF contributors
+# SPDX-License-Identifier: MIT OR Apache-2.0
+#
+# A simplified version of client_exmample.py from the Python implementation:
+# https://github.com/theupdateframework/python-tuf/blob/v1.0.0/examples/client_example/client_example.py
+
+import argparse
+
+from typing import List
+from pathlib import Path
+
+from tuf.ngclient import Updater
+
+
+def update_client(repo: str, targets: List[str]):
+ metadata_dir = Path("tufrepo/metadata/current")
+ targets_dir = Path("tuftargets")
+ targets_dir.mkdir()
+ updater = Updater(
+ metadata_dir=str(metadata_dir),
+ metadata_base_url=f"{repo}/repository/",
+ target_base_url=f"{repo}/repository/targets/",
+ target_dir=str(targets_dir),
+ )
+
+ updater.refresh()
+ for target in targets:
+ info = updater.get_targetinfo(target)
+ assert not updater.find_cached_target(info)
+ updater.download_target(info)
+
+
+def parse_arguments():
+ parser = argparse.ArgumentParser(
+ description="Retrieve file from TUF repository.")
+
+ parser.add_argument(
+ "-r",
+ "--repo",
+ type=str,
+ required=True,
+ metavar="<URI>",
+ help="Specify the remote repository's URI"
+ " (e.g., http://www.example.com:8001/tuf/). The client retrieves"
+ " updates from the remote repository.",
+ )
+
+ parser.add_argument(
+ "targets",
+ nargs="+",
+ metavar="<file>",
+ help="Specify"
+ " the target files to retrieve from the specified TUF repository.",
+ )
+
+ parsed_arguments = parser.parse_args()
+ return parsed_arguments
+
+
+def main():
+ arguments = parse_arguments()
+ update_client(arguments.repo, arguments.targets)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/generate.py b/client/python_interop/testdata/python-tuf-v3.0.0/generate.py
new file mode 100755
index 0000000..e494d18
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/generate.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+#
+# A script to generate TUF repository files.
+#
+# A modification of generate.py from the Python implementation:
+# https://github.com/theupdateframework/tuf/blob/v0.9.9/tests/repository_data/generate.py
+# Updated a bit for the v1.0.0 version:
+# https://github.com/theupdateframework/python-tuf/blob/v1.0.0/examples/repo_example/basic_repo.py
+
+import datetime
+import optparse
+import shutil
+
+from pathlib import Path
+from typing import Dict
+
+from securesystemslib.keys import generate_ed25519_key
+from securesystemslib.signer import SSlibSigner, SSlibKey
+from tuf.api.metadata import (
+ Metadata,
+ MetaFile,
+ Role,
+ Root,
+ Snapshot,
+ TargetFile,
+ Targets,
+ Timestamp,
+)
+from tuf.api.serialization.json import JSONSerializer
+
+
+SPEC_VERSION = "1.0.19"
+PRETTY = JSONSerializer(compact=False)
+EXPIRY = datetime.datetime(2030, 1, 1, 0, 0) # Far enough in the future
+ROLES = set(("targets", "snapshot", "timestamp", "root"))
+
+
+def make_targets(target_dir: Path, consistent_snapshot: bool) -> Dict[str, TargetFile]:
+ targets = {}
+ for target in (Path("file1.txt"), Path("dir/file2.txt")):
+ target_fspath = target_dir / target
+ target_fspath.parent.mkdir(parents=True, exist_ok=True)
+ # file contents are the file name
+ target_fspath.write_text(target.name)
+ target_file_info = TargetFile.from_file(
+ str(target), str(target_fspath))
+ if consistent_snapshot:
+ digest = next(iter(target_file_info.hashes.values()))
+ shutil.move(target_fspath, target_fspath.parent /
+ f"{digest}.{target.name}")
+ targets[str(target)] = target_file_info
+ return targets
+
+
+def make_test_repo(repo_dir: Path, consistent_snapshot: bool):
+ """Create a test repository in `repo_dir`.
+
+ Two targets:
+ - `file1.txt`
+ - `dir/file2.txt`
+ """
+ roles: Dict[str, Metadata] = {}
+
+ targets: Dict[str, TargetFile] = {}
+ target_dir = repo_dir / "targets"
+ target_dir.mkdir()
+ targets = make_targets(target_dir, consistent_snapshot)
+ target_metadata = Targets(
+ version=1, spec_version=SPEC_VERSION, expires=EXPIRY, targets=targets
+ )
+ roles["targets"] = Metadata[Targets](target_metadata, {})
+
+ snapshot_metadata = Snapshot(
+ version=1,
+ spec_version=SPEC_VERSION,
+ expires=EXPIRY,
+ meta={"targets.json": MetaFile(version=1)},
+ )
+ roles["snapshot"] = Metadata[Snapshot](snapshot_metadata, {})
+
+ timestamp_metadata = Timestamp(
+ version=1,
+ spec_version=SPEC_VERSION,
+ expires=EXPIRY,
+ snapshot_meta=MetaFile(version=1),
+ )
+ roles["timestamp"] = Metadata[Timestamp](timestamp_metadata, {})
+
+ keys = {name: generate_ed25519_key() for name in ROLES}
+
+ root_metadata = Root(
+ version=1,
+ spec_version=SPEC_VERSION,
+ expires=EXPIRY,
+ keys={
+ key["keyid"]: SSlibKey.from_securesystemslib_key(key) for key in keys.values()
+ },
+ roles={role: Role([key["keyid"]], threshold=1)
+ for role, key in keys.items()},
+ consistent_snapshot=consistent_snapshot,
+ )
+ roles["root"] = Metadata[Root](root_metadata, {})
+
+ # Write the metadata files
+ metadata_dir = repo_dir / "metadata"
+ metadata_dir.mkdir()
+ for name in ["root", "targets", "snapshot", "timestamp"]:
+ role = roles[name]
+ key = keys[role.signed.type]
+ signer = SSlibSigner(key)
+ role.sign(signer)
+
+ if name == "root" or (consistent_snapshot and name != "timestamp"):
+ filename = f"{role.signed.version}.{name}.json"
+ else:
+ filename = f"{name}.json"
+ role.to_file(str(metadata_dir / filename), serializer=PRETTY)
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option(
+ "-c",
+ "--consistent-snapshot",
+ action="store_true",
+ dest="consistent_snapshot",
+ help="Generate consistent snapshot",
+ default=False,
+ )
+ (options, args) = parser.parse_args()
+
+ repo_dir = Path("repository")
+ repo_dir.mkdir()
+ make_test_repo(repo_dir, options.consistent_snapshot)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/generate.sh b/client/python_interop/testdata/python-tuf-v3.0.0/generate.sh
new file mode 100755
index 0000000..094fa41
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/generate.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# A script to generate TUF repository files using the Python implementation.
+#
+# A list of generated files is printed to STDERR and a tar of the files to STDOUT.
+
+set -e
+
+main() {
+ local dir="$(mktemp -d)"
+ trap "rm -rf ${dir}" EXIT
+
+ pushd "${dir}" >/dev/null
+ generate_consistent
+ generate_non_consistent
+ list_files >&2
+ tar c .
+ popd >/dev/null
+}
+
+generate_consistent() {
+ mkdir "with-consistent-snapshot"
+ pushd "with-consistent-snapshot" >/dev/null
+ python3 /generate.py --consistent-snapshot
+ popd >/dev/null
+}
+
+generate_non_consistent() {
+ mkdir "without-consistent-snapshot"
+ pushd "without-consistent-snapshot" >/dev/null
+ python3 /generate.py
+ popd >/dev/null
+}
+
+list_files() {
+ echo "Files generated:"
+ tree
+}
+
+main $@
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.root.json b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.root.json
new file mode 100755
index 0000000..e0284c5
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.root.json
@@ -0,0 +1,71 @@
+{
+ "signatures": [
+ {
+ "keyid": "c3fcd2035fd2d29b36a29c9e9a52b199f85492b7cf8f9730b865dfa989eb47c8",
+ "sig": "1c02cfc95564dcce804f2c48de3048881e89be6b49ad3db208a870a5bd69bc7749c809613f44edadd744a01b0ead25753853dbacb955e6b29b59672f654c1809"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2030-01-01T00:00:00Z",
+ "keys": {
+ "1d98f2950591737d5f49f0a00d00890ee385ed272285fba1cb89553a37813fcf": {
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "4b937cac5c77ce5ebd18b4ce252f92b1da3e8640a033c34726fe7a821c3acd45"
+ },
+ "scheme": "ed25519"
+ },
+ "c3fcd2035fd2d29b36a29c9e9a52b199f85492b7cf8f9730b865dfa989eb47c8": {
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "0cb442e869c9aead667909ba27ce7ed2c87d396fb92a180ec02b37c64f0f0404"
+ },
+ "scheme": "ed25519"
+ },
+ "e45cfc40f8cae2e4d760b4ecec0b59238fd2fd769391998920927eec825e7829": {
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "ce00487915590a5748b9c08ca720c26cd233424a05f72105ccaa49411b662e83"
+ },
+ "scheme": "ed25519"
+ },
+ "f7662dc3e9eebfb74d0e53f56941c6cf5668967110d2af28b41f9371544ccd0b": {
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "64017bbf887018ecb927029b7524ea9b50d201e232274843384204c5e6e25c06"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "c3fcd2035fd2d29b36a29c9e9a52b199f85492b7cf8f9730b865dfa989eb47c8"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "e45cfc40f8cae2e4d760b4ecec0b59238fd2fd769391998920927eec825e7829"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "f7662dc3e9eebfb74d0e53f56941c6cf5668967110d2af28b41f9371544ccd0b"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "1d98f2950591737d5f49f0a00d00890ee385ed272285fba1cb89553a37813fcf"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.19",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.snapshot.json b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.snapshot.json
new file mode 100755
index 0000000..1ed1b9c
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e45cfc40f8cae2e4d760b4ecec0b59238fd2fd769391998920927eec825e7829",
+ "sig": "f86a214877f8009aee97b2fc0f96131714f07fa8e2e65b153f08be6d3eba1533b82fb2b774cb82d0b7969f2d5d94071c387f4891276484ba6f5e7a0e11b58f09"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2030-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.19",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.targets.json b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.targets.json
new file mode 100755
index 0000000..84d2e05
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/1.targets.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "f7662dc3e9eebfb74d0e53f56941c6cf5668967110d2af28b41f9371544ccd0b",
+ "sig": "708400f70b965991928ea89f925227c97886f673a4e6a32e57062fc533580812fd1b65989fd751a4c3959ff716d8f0e7a09da0d971fa726553f5cc3ea7ff600e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2030-01-01T00:00:00Z",
+ "spec_version": "1.0.19",
+ "targets": {
+ "dir/file2.txt": {
+ "hashes": {
+ "sha256": "04e2f59431a9d219321baf7d21b8cc797d7615dc3e9515c782c49d2075658701"
+ },
+ "length": 9
+ },
+ "file1.txt": {
+ "hashes": {
+ "sha256": "55ae75d991c770d8f3ef07cbfde124ffce9c420da5db6203afab700b27e10cf9"
+ },
+ "length": 9
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/timestamp.json b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/timestamp.json
new file mode 100755
index 0000000..02f509b
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/metadata/timestamp.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "1d98f2950591737d5f49f0a00d00890ee385ed272285fba1cb89553a37813fcf",
+ "sig": "4453d37f5105ba74f203c72e241c2f3a61df82f37f66e80ba43523534c8bbe30eb06b675133662a38544c87e1087a013d2a786a718ce2f1f338f4177bedccf0e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2030-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.19",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/targets/55ae75d991c770d8f3ef07cbfde124ffce9c420da5db6203afab700b27e10cf9.file1.txt b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/targets/55ae75d991c770d8f3ef07cbfde124ffce9c420da5db6203afab700b27e10cf9.file1.txt
new file mode 100644
index 0000000..39cd576
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/targets/55ae75d991c770d8f3ef07cbfde124ffce9c420da5db6203afab700b27e10cf9.file1.txt
@@ -0,0 +1 @@
+file1.txt \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/targets/dir/04e2f59431a9d219321baf7d21b8cc797d7615dc3e9515c782c49d2075658701.file2.txt b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/targets/dir/04e2f59431a9d219321baf7d21b8cc797d7615dc3e9515c782c49d2075658701.file2.txt
new file mode 100644
index 0000000..c3ee11c
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/with-consistent-snapshot/repository/targets/dir/04e2f59431a9d219321baf7d21b8cc797d7615dc3e9515c782c49d2075658701.file2.txt
@@ -0,0 +1 @@
+file2.txt \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/1.root.json b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/1.root.json
new file mode 100755
index 0000000..f17ee71
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/1.root.json
@@ -0,0 +1,71 @@
+{
+ "signatures": [
+ {
+ "keyid": "6a6777bdb8d607b2ca394fab722440c9b82557d4c3d694b69ed37f880d0bb359",
+ "sig": "32a8f36d461328175f3f48852c8c3f5c0b2202e263e66b40171b3aeca4b485e2d21e29b949da816f1f899f537450fa989a2e7356f7469b9d59b9c37eb0388b06"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2030-01-01T00:00:00Z",
+ "keys": {
+ "6a6777bdb8d607b2ca394fab722440c9b82557d4c3d694b69ed37f880d0bb359": {
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "16b7d48bb6bf00dd8d3bcbea04c5937f85334dcc4e5be73b62ec551778962def"
+ },
+ "scheme": "ed25519"
+ },
+ "94995c1ecba97e06fc8c65fb881504c62c5906000cbf0fc39bf2ed910690ce91": {
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e8a65a4317ab8a37a8db8ce3785b8745516282425d9b3d3533605a62c729fc9c"
+ },
+ "scheme": "ed25519"
+ },
+ "b2100105c1dcea16a93bc583fa0a3a6936b5375b1f944eaf6bc8e806c0f43046": {
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "34beb06ede086112f38aef9131eee9e480e0d3670e801b97d2e99add6ac0856f"
+ },
+ "scheme": "ed25519"
+ },
+ "c74276233340a505633ab55237d3ff7c0e2fedb938806d0226d2331e8c5f98f4": {
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "b4135ebeaf0850dad533cdc0a2721af9941503ea2009eace713685a47b15be32"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "6a6777bdb8d607b2ca394fab722440c9b82557d4c3d694b69ed37f880d0bb359"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "94995c1ecba97e06fc8c65fb881504c62c5906000cbf0fc39bf2ed910690ce91"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b2100105c1dcea16a93bc583fa0a3a6936b5375b1f944eaf6bc8e806c0f43046"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "c74276233340a505633ab55237d3ff7c0e2fedb938806d0226d2331e8c5f98f4"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.19",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/snapshot.json b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/snapshot.json
new file mode 100755
index 0000000..d1639b4
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "94995c1ecba97e06fc8c65fb881504c62c5906000cbf0fc39bf2ed910690ce91",
+ "sig": "618e99e47678fa0ccc5b664e5b4230de9f529d6456f376ef8ee55a8d94cf4e592e5b1ab5d6789838cbc1e7724af544866be48cd49cb32d3ac9485bb02ec06a0d"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2030-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.19",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/targets.json b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/targets.json
new file mode 100755
index 0000000..e97913e
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/targets.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "b2100105c1dcea16a93bc583fa0a3a6936b5375b1f944eaf6bc8e806c0f43046",
+ "sig": "c4ad17cbc0bc54410c135dc74b638c51e1cafbb64f991b0a3e90a526eb63a162c33a73d6fde01ba84775522eb5c724f43c9f3efd7afd5bc469a5814399e31a0f"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2030-01-01T00:00:00Z",
+ "spec_version": "1.0.19",
+ "targets": {
+ "dir/file2.txt": {
+ "hashes": {
+ "sha256": "04e2f59431a9d219321baf7d21b8cc797d7615dc3e9515c782c49d2075658701"
+ },
+ "length": 9
+ },
+ "file1.txt": {
+ "hashes": {
+ "sha256": "55ae75d991c770d8f3ef07cbfde124ffce9c420da5db6203afab700b27e10cf9"
+ },
+ "length": 9
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/timestamp.json b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/timestamp.json
new file mode 100755
index 0000000..08e8479
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/metadata/timestamp.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "c74276233340a505633ab55237d3ff7c0e2fedb938806d0226d2331e8c5f98f4",
+ "sig": "f1191748bc8b74751c7e6c3bb0a85a02d54f92214bda78cd3230edf826a9bbf0af15dbf214424e7fb6c7b1a84054ed7a6bb57b05169c52ac0e63da099b9fdd02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2030-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.19",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/targets/dir/file2.txt b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/targets/dir/file2.txt
new file mode 100644
index 0000000..c3ee11c
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/targets/dir/file2.txt
@@ -0,0 +1 @@
+file2.txt \ No newline at end of file
diff --git a/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/targets/file1.txt b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/targets/file1.txt
new file mode 100644
index 0000000..39cd576
--- /dev/null
+++ b/client/python_interop/testdata/python-tuf-v3.0.0/without-consistent-snapshot/repository/targets/file1.txt
@@ -0,0 +1 @@
+file1.txt \ No newline at end of file
diff --git a/client/remote_store.go b/client/remote_store.go
new file mode 100644
index 0000000..17a63fc
--- /dev/null
+++ b/client/remote_store.go
@@ -0,0 +1,109 @@
+package client
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "path"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type HTTPRemoteOptions struct {
+ MetadataPath string
+ TargetsPath string
+ UserAgent string
+ Retries *HTTPRemoteRetries
+}
+
+type HTTPRemoteRetries struct {
+ Delay time.Duration
+ Total time.Duration
+}
+
+var DefaultHTTPRetries = &HTTPRemoteRetries{
+ Delay: time.Second,
+ Total: 10 * time.Second,
+}
+
+func HTTPRemoteStore(baseURL string, opts *HTTPRemoteOptions, client *http.Client) (RemoteStore, error) {
+ if !strings.HasPrefix(baseURL, "http") {
+ return nil, ErrInvalidURL{baseURL}
+ }
+ if opts == nil {
+ opts = &HTTPRemoteOptions{}
+ }
+ if opts.TargetsPath == "" {
+ opts.TargetsPath = "targets"
+ }
+ if client == nil {
+ client = http.DefaultClient
+ }
+ return &httpRemoteStore{baseURL, opts, client}, nil
+}
+
+type httpRemoteStore struct {
+ baseURL string
+ opts *HTTPRemoteOptions
+ cli *http.Client
+}
+
+func (h *httpRemoteStore) GetMeta(name string) (io.ReadCloser, int64, error) {
+ return h.get(path.Join(h.opts.MetadataPath, name))
+}
+
+func (h *httpRemoteStore) GetTarget(name string) (io.ReadCloser, int64, error) {
+ return h.get(path.Join(h.opts.TargetsPath, name))
+}
+
+func (h *httpRemoteStore) get(s string) (io.ReadCloser, int64, error) {
+ u := h.url(s)
+ req, err := http.NewRequest("GET", u, nil)
+ if err != nil {
+ return nil, 0, err
+ }
+ if h.opts.UserAgent != "" {
+ req.Header.Set("User-Agent", h.opts.UserAgent)
+ }
+ var res *http.Response
+ if r := h.opts.Retries; r != nil {
+ for start := time.Now(); time.Since(start) < r.Total; time.Sleep(r.Delay) {
+ res, err = h.cli.Do(req)
+ if err == nil && (res.StatusCode < 500 || res.StatusCode > 599) {
+ break
+ }
+ }
+ } else {
+ res, err = h.cli.Do(req)
+ }
+ if err != nil {
+ return nil, 0, err
+ }
+
+ if res.StatusCode == http.StatusNotFound {
+ res.Body.Close()
+ return nil, 0, ErrNotFound{s}
+ } else if res.StatusCode != http.StatusOK {
+ res.Body.Close()
+ return nil, 0, &url.Error{
+ Op: "GET",
+ URL: u,
+ Err: fmt.Errorf("unexpected HTTP status %d", res.StatusCode),
+ }
+ }
+
+ size, err := strconv.ParseInt(res.Header.Get("Content-Length"), 10, 0)
+ if err != nil {
+ return res.Body, -1, nil
+ }
+ return res.Body, size, nil
+}
+
+func (h *httpRemoteStore) url(path string) string {
+ if !strings.HasPrefix(path, "/") {
+ path = "/" + path
+ }
+ return h.baseURL + path
+}
diff --git a/client/testdata/Published1Time/client/metadata/current/1.root.json b/client/testdata/Published1Time/client/metadata/current/1.root.json
new file mode 100644
index 0000000..6b72b01
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "fc18813db531963ba283e63056be9f5e303e6e4f3d4cdc06301ff2c7fdf67e6850881439eb6e5479a4c874f7e5837972cb1debe02e0613a0ef229a9545a0f60d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:36Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/current/1.snapshot.json b/client/testdata/Published1Time/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..0c760c1
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "bff908e74d135b9877db003f078a863701f64ee477f2f9521628396ce14a460d7cbd0b5f81682c7dd082512f18d0dedb5c593b012856f496012445cfd16d6706"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/current/1.targets.json b/client/testdata/Published1Time/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..59a8059
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "bc7c651efcc8939b9e8dedaa8655ddb95b4f0e6a9336502dffe82e569e035c1274415c4075eef318f964bceb834d85a763f58e5c34d8e3e88cd603e6aec5ec05"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:36Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/current/1.timestamp.json b/client/testdata/Published1Time/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..afc2722
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "e4e6dc4a5e6effa5e9a85a2cb22e29847ee91dec8e5f2f57bd50c573a47e9cc95566f703ef0f9ecf4e0ee82804ad92412a226524924f4fb99724c949fffd1e05"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "2bf3861b5e3be6cb82dc3820cbb128874d18f88e2f08ef81f6b385f3e3c7900b",
+ "sha512": "c178334cff4dbb2dec4eea043b135a6339661a3c189dc0a689c6f92f150bd15474de5189c513a8c68bf831ea15656142c4ce8de0ae54ab23806fa6af4683783d"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/current/root.json b/client/testdata/Published1Time/client/metadata/current/root.json
new file mode 100644
index 0000000..6b72b01
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "fc18813db531963ba283e63056be9f5e303e6e4f3d4cdc06301ff2c7fdf67e6850881439eb6e5479a4c874f7e5837972cb1debe02e0613a0ef229a9545a0f60d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:36Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/current/snapshot.json b/client/testdata/Published1Time/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..0c760c1
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "bff908e74d135b9877db003f078a863701f64ee477f2f9521628396ce14a460d7cbd0b5f81682c7dd082512f18d0dedb5c593b012856f496012445cfd16d6706"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/current/targets.json b/client/testdata/Published1Time/client/metadata/current/targets.json
new file mode 100644
index 0000000..59a8059
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "bc7c651efcc8939b9e8dedaa8655ddb95b4f0e6a9336502dffe82e569e035c1274415c4075eef318f964bceb834d85a763f58e5c34d8e3e88cd603e6aec5ec05"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:36Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/current/timestamp.json b/client/testdata/Published1Time/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..afc2722
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "e4e6dc4a5e6effa5e9a85a2cb22e29847ee91dec8e5f2f57bd50c573a47e9cc95566f703ef0f9ecf4e0ee82804ad92412a226524924f4fb99724c949fffd1e05"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "2bf3861b5e3be6cb82dc3820cbb128874d18f88e2f08ef81f6b385f3e3c7900b",
+ "sha512": "c178334cff4dbb2dec4eea043b135a6339661a3c189dc0a689c6f92f150bd15474de5189c513a8c68bf831ea15656142c4ce8de0ae54ab23806fa6af4683783d"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/previous/1.root.json b/client/testdata/Published1Time/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..6b72b01
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "fc18813db531963ba283e63056be9f5e303e6e4f3d4cdc06301ff2c7fdf67e6850881439eb6e5479a4c874f7e5837972cb1debe02e0613a0ef229a9545a0f60d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:36Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/previous/1.snapshot.json b/client/testdata/Published1Time/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..0c760c1
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "bff908e74d135b9877db003f078a863701f64ee477f2f9521628396ce14a460d7cbd0b5f81682c7dd082512f18d0dedb5c593b012856f496012445cfd16d6706"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/previous/1.targets.json b/client/testdata/Published1Time/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..59a8059
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "bc7c651efcc8939b9e8dedaa8655ddb95b4f0e6a9336502dffe82e569e035c1274415c4075eef318f964bceb834d85a763f58e5c34d8e3e88cd603e6aec5ec05"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:36Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/previous/1.timestamp.json b/client/testdata/Published1Time/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..afc2722
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "e4e6dc4a5e6effa5e9a85a2cb22e29847ee91dec8e5f2f57bd50c573a47e9cc95566f703ef0f9ecf4e0ee82804ad92412a226524924f4fb99724c949fffd1e05"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "2bf3861b5e3be6cb82dc3820cbb128874d18f88e2f08ef81f6b385f3e3c7900b",
+ "sha512": "c178334cff4dbb2dec4eea043b135a6339661a3c189dc0a689c6f92f150bd15474de5189c513a8c68bf831ea15656142c4ce8de0ae54ab23806fa6af4683783d"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/previous/root.json b/client/testdata/Published1Time/client/metadata/previous/root.json
new file mode 100644
index 0000000..6b72b01
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "fc18813db531963ba283e63056be9f5e303e6e4f3d4cdc06301ff2c7fdf67e6850881439eb6e5479a4c874f7e5837972cb1debe02e0613a0ef229a9545a0f60d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:36Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/previous/snapshot.json b/client/testdata/Published1Time/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..0c760c1
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "bff908e74d135b9877db003f078a863701f64ee477f2f9521628396ce14a460d7cbd0b5f81682c7dd082512f18d0dedb5c593b012856f496012445cfd16d6706"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/previous/targets.json b/client/testdata/Published1Time/client/metadata/previous/targets.json
new file mode 100644
index 0000000..59a8059
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "bc7c651efcc8939b9e8dedaa8655ddb95b4f0e6a9336502dffe82e569e035c1274415c4075eef318f964bceb834d85a763f58e5c34d8e3e88cd603e6aec5ec05"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:36Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/client/metadata/previous/timestamp.json b/client/testdata/Published1Time/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..afc2722
--- /dev/null
+++ b/client/testdata/Published1Time/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "e4e6dc4a5e6effa5e9a85a2cb22e29847ee91dec8e5f2f57bd50c573a47e9cc95566f703ef0f9ecf4e0ee82804ad92412a226524924f4fb99724c949fffd1e05"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "2bf3861b5e3be6cb82dc3820cbb128874d18f88e2f08ef81f6b385f3e3c7900b",
+ "sha512": "c178334cff4dbb2dec4eea043b135a6339661a3c189dc0a689c6f92f150bd15474de5189c513a8c68bf831ea15656142c4ce8de0ae54ab23806fa6af4683783d"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/hash.txt b/client/testdata/Published1Time/hash.txt
new file mode 100644
index 0000000..e5b238d
--- /dev/null
+++ b/client/testdata/Published1Time/hash.txt
@@ -0,0 +1 @@
+4b336d13d5ef371c39a574265b9c8f5dcf7a6031962a8f2ef9238e8b45d8019b \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata.staged/1.root.json b/client/testdata/Published1Time/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..6b72b01
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "fc18813db531963ba283e63056be9f5e303e6e4f3d4cdc06301ff2c7fdf67e6850881439eb6e5479a4c874f7e5837972cb1debe02e0613a0ef229a9545a0f60d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:36Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata.staged/1.snapshot.json b/client/testdata/Published1Time/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..0c760c1
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "bff908e74d135b9877db003f078a863701f64ee477f2f9521628396ce14a460d7cbd0b5f81682c7dd082512f18d0dedb5c593b012856f496012445cfd16d6706"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata.staged/1.targets.json b/client/testdata/Published1Time/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..59a8059
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "bc7c651efcc8939b9e8dedaa8655ddb95b4f0e6a9336502dffe82e569e035c1274415c4075eef318f964bceb834d85a763f58e5c34d8e3e88cd603e6aec5ec05"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:36Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata.staged/1.timestamp.json b/client/testdata/Published1Time/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..afc2722
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "e4e6dc4a5e6effa5e9a85a2cb22e29847ee91dec8e5f2f57bd50c573a47e9cc95566f703ef0f9ecf4e0ee82804ad92412a226524924f4fb99724c949fffd1e05"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "2bf3861b5e3be6cb82dc3820cbb128874d18f88e2f08ef81f6b385f3e3c7900b",
+ "sha512": "c178334cff4dbb2dec4eea043b135a6339661a3c189dc0a689c6f92f150bd15474de5189c513a8c68bf831ea15656142c4ce8de0ae54ab23806fa6af4683783d"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata.staged/root.json b/client/testdata/Published1Time/server/metadata.staged/root.json
new file mode 100644
index 0000000..6b72b01
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata.staged/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "fc18813db531963ba283e63056be9f5e303e6e4f3d4cdc06301ff2c7fdf67e6850881439eb6e5479a4c874f7e5837972cb1debe02e0613a0ef229a9545a0f60d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:36Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata.staged/snapshot.json b/client/testdata/Published1Time/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..0c760c1
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "bff908e74d135b9877db003f078a863701f64ee477f2f9521628396ce14a460d7cbd0b5f81682c7dd082512f18d0dedb5c593b012856f496012445cfd16d6706"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata.staged/targets.json b/client/testdata/Published1Time/server/metadata.staged/targets.json
new file mode 100644
index 0000000..59a8059
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "bc7c651efcc8939b9e8dedaa8655ddb95b4f0e6a9336502dffe82e569e035c1274415c4075eef318f964bceb834d85a763f58e5c34d8e3e88cd603e6aec5ec05"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:36Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata.staged/timestamp.json b/client/testdata/Published1Time/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..afc2722
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "e4e6dc4a5e6effa5e9a85a2cb22e29847ee91dec8e5f2f57bd50c573a47e9cc95566f703ef0f9ecf4e0ee82804ad92412a226524924f4fb99724c949fffd1e05"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "2bf3861b5e3be6cb82dc3820cbb128874d18f88e2f08ef81f6b385f3e3c7900b",
+ "sha512": "c178334cff4dbb2dec4eea043b135a6339661a3c189dc0a689c6f92f150bd15474de5189c513a8c68bf831ea15656142c4ce8de0ae54ab23806fa6af4683783d"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata/1.root.json b/client/testdata/Published1Time/server/metadata/1.root.json
new file mode 100644
index 0000000..6b72b01
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "fc18813db531963ba283e63056be9f5e303e6e4f3d4cdc06301ff2c7fdf67e6850881439eb6e5479a4c874f7e5837972cb1debe02e0613a0ef229a9545a0f60d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:36Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata/1.snapshot.json b/client/testdata/Published1Time/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..0c760c1
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "bff908e74d135b9877db003f078a863701f64ee477f2f9521628396ce14a460d7cbd0b5f81682c7dd082512f18d0dedb5c593b012856f496012445cfd16d6706"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata/1.targets.json b/client/testdata/Published1Time/server/metadata/1.targets.json
new file mode 100644
index 0000000..59a8059
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "bc7c651efcc8939b9e8dedaa8655ddb95b4f0e6a9336502dffe82e569e035c1274415c4075eef318f964bceb834d85a763f58e5c34d8e3e88cd603e6aec5ec05"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:36Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata/1.timestamp.json b/client/testdata/Published1Time/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..afc2722
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "e4e6dc4a5e6effa5e9a85a2cb22e29847ee91dec8e5f2f57bd50c573a47e9cc95566f703ef0f9ecf4e0ee82804ad92412a226524924f4fb99724c949fffd1e05"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "2bf3861b5e3be6cb82dc3820cbb128874d18f88e2f08ef81f6b385f3e3c7900b",
+ "sha512": "c178334cff4dbb2dec4eea043b135a6339661a3c189dc0a689c6f92f150bd15474de5189c513a8c68bf831ea15656142c4ce8de0ae54ab23806fa6af4683783d"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata/root.json b/client/testdata/Published1Time/server/metadata/root.json
new file mode 100644
index 0000000..6b72b01
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "fc18813db531963ba283e63056be9f5e303e6e4f3d4cdc06301ff2c7fdf67e6850881439eb6e5479a4c874f7e5837972cb1debe02e0613a0ef229a9545a0f60d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:36Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata/snapshot.json b/client/testdata/Published1Time/server/metadata/snapshot.json
new file mode 100644
index 0000000..0c760c1
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "bff908e74d135b9877db003f078a863701f64ee477f2f9521628396ce14a460d7cbd0b5f81682c7dd082512f18d0dedb5c593b012856f496012445cfd16d6706"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata/targets.json b/client/testdata/Published1Time/server/metadata/targets.json
new file mode 100644
index 0000000..59a8059
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "bc7c651efcc8939b9e8dedaa8655ddb95b4f0e6a9336502dffe82e569e035c1274415c4075eef318f964bceb834d85a763f58e5c34d8e3e88cd603e6aec5ec05"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:36Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time/server/metadata/timestamp.json b/client/testdata/Published1Time/server/metadata/timestamp.json
new file mode 100644
index 0000000..afc2722
--- /dev/null
+++ b/client/testdata/Published1Time/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "e4e6dc4a5e6effa5e9a85a2cb22e29847ee91dec8e5f2f57bd50c573a47e9cc95566f703ef0f9ecf4e0ee82804ad92412a226524924f4fb99724c949fffd1e05"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:36Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "2bf3861b5e3be6cb82dc3820cbb128874d18f88e2f08ef81f6b385f3e3c7900b",
+ "sha512": "c178334cff4dbb2dec4eea043b135a6339661a3c189dc0a689c6f92f150bd15474de5189c513a8c68bf831ea15656142c4ce8de0ae54ab23806fa6af4683783d"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.root.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.snapshot.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.targets.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.timestamp.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/root.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/snapshot.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/targets.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/timestamp.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.root.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.snapshot.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.targets.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.timestamp.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/root.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/snapshot.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/targets.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/timestamp.json b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/hash.txt b/client/testdata/Published1Time_backwardRootVersion/hash.txt
new file mode 100644
index 0000000..eb46ce4
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/hash.txt
@@ -0,0 +1 @@
+d8e9b9784ec1037e1c2724ae98cf9b8ef3e940e4d61e9f437d003d9f9e2d8d5b \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.root.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.snapshot.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.targets.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.timestamp.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/root.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/snapshot.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/targets.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/timestamp.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.snapshot.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.targets.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.timestamp.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata/2.root.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata/2.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata/2.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata/root.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata/snapshot.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata/targets.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_backwardRootVersion/server/metadata/timestamp.json b/client/testdata/Published1Time_backwardRootVersion/server/metadata/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published1Time_backwardRootVersion/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/current/1.snapshot.json b/client/testdata/Published1Time_client_no_root/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/current/1.targets.json b/client/testdata/Published1Time_client_no_root/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/current/1.timestamp.json b/client/testdata/Published1Time_client_no_root/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/current/snapshot.json b/client/testdata/Published1Time_client_no_root/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/current/targets.json b/client/testdata/Published1Time_client_no_root/client/metadata/current/targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/current/timestamp.json b/client/testdata/Published1Time_client_no_root/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.root.json b/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..6afde0f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "257396e371baf489e38653fef8cf0c98d1915a1f61f5bfbb0e3a03bad7c863795b738cdddc6b6899354447cfed0de0f79f8eaeec7acd509322009ee90c49b20d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T02:30:57Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.snapshot.json b/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.targets.json b/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.timestamp.json b/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/previous/root.json b/client/testdata/Published1Time_client_no_root/client/metadata/previous/root.json
new file mode 100644
index 0000000..6afde0f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "257396e371baf489e38653fef8cf0c98d1915a1f61f5bfbb0e3a03bad7c863795b738cdddc6b6899354447cfed0de0f79f8eaeec7acd509322009ee90c49b20d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T02:30:57Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/previous/snapshot.json b/client/testdata/Published1Time_client_no_root/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/previous/targets.json b/client/testdata/Published1Time_client_no_root/client/metadata/previous/targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/client/metadata/previous/timestamp.json b/client/testdata/Published1Time_client_no_root/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/hash.txt b/client/testdata/Published1Time_client_no_root/hash.txt
new file mode 100644
index 0000000..adf9cc0
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/hash.txt
@@ -0,0 +1 @@
+fccdb5eadd5e09460c9ec806e051785d855da2c4862cf9cec2f63401c866bc34 \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.root.json b/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.snapshot.json b/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.targets.json b/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.timestamp.json b/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata.staged/root.json b/client/testdata/Published1Time_client_no_root/server/metadata.staged/root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata.staged/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata.staged/snapshot.json b/client/testdata/Published1Time_client_no_root/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata.staged/targets.json b/client/testdata/Published1Time_client_no_root/server/metadata.staged/targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata.staged/timestamp.json b/client/testdata/Published1Time_client_no_root/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata/1.root.json b/client/testdata/Published1Time_client_no_root/server/metadata/1.root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata/1.snapshot.json b/client/testdata/Published1Time_client_no_root/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata/1.targets.json b/client/testdata/Published1Time_client_no_root/server/metadata/1.targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata/1.timestamp.json b/client/testdata/Published1Time_client_no_root/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata/root.json b/client/testdata/Published1Time_client_no_root/server/metadata/root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata/snapshot.json b/client/testdata/Published1Time_client_no_root/server/metadata/snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata/targets.json b/client/testdata/Published1Time_client_no_root/server/metadata/targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_no_root/server/metadata/timestamp.json b/client/testdata/Published1Time_client_no_root/server/metadata/timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_no_root/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/current/root.json b/client/testdata/Published1Time_client_root_only/client/metadata/current/root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.root.json b/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..6afde0f
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "257396e371baf489e38653fef8cf0c98d1915a1f61f5bfbb0e3a03bad7c863795b738cdddc6b6899354447cfed0de0f79f8eaeec7acd509322009ee90c49b20d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T02:30:57Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.snapshot.json b/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..e27a8a2
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "60a5724ac31af58ece866b723ab121a168b9e93f0033a98dd8449e6550f4d897db0329cdb135fd3a016c8c375e4177c16f1f4d3ada550eb6d06a52b5c84fbb07"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T02:30:57Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.targets.json b/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..850f22a
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "ecb8da4380f992d0d80cbcb78c31c91baf171dad346725fcbf6b1487f52653fc9a2f1ab46a28d32a733a48badbbd316814dcda52cc49cf3bf6fb415403ce7a0a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T02:30:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.timestamp.json b/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..4d4abc0
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "4e4f72264b522cf8e2867fcaf1a6b1463d43bdf58027fb5770b6d611ae68a9f36b93ce5a50f874744ef1d7b72a11ad685f24081511c306fd7118dc60c90bdf08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T02:30:57Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "195c327842ae9601900016a50d0536a05136bfbf55ddb70657427ae12ed52181",
+ "sha512": "8deed35dcd9d634fcce94c42049829078c15a57d35ee83b15c201b6a0c80111fefba49cca522dedb1d79bc000393bf6c45b72d485f9d027f7c5a3f8922734a5c"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/previous/root.json b/client/testdata/Published1Time_client_root_only/client/metadata/previous/root.json
new file mode 100644
index 0000000..6afde0f
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "257396e371baf489e38653fef8cf0c98d1915a1f61f5bfbb0e3a03bad7c863795b738cdddc6b6899354447cfed0de0f79f8eaeec7acd509322009ee90c49b20d"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T02:30:57Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/previous/snapshot.json b/client/testdata/Published1Time_client_root_only/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..e27a8a2
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "60a5724ac31af58ece866b723ab121a168b9e93f0033a98dd8449e6550f4d897db0329cdb135fd3a016c8c375e4177c16f1f4d3ada550eb6d06a52b5c84fbb07"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T02:30:57Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/previous/targets.json b/client/testdata/Published1Time_client_root_only/client/metadata/previous/targets.json
new file mode 100644
index 0000000..850f22a
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "ecb8da4380f992d0d80cbcb78c31c91baf171dad346725fcbf6b1487f52653fc9a2f1ab46a28d32a733a48badbbd316814dcda52cc49cf3bf6fb415403ce7a0a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T02:30:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/client/metadata/previous/timestamp.json b/client/testdata/Published1Time_client_root_only/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..4d4abc0
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "4e4f72264b522cf8e2867fcaf1a6b1463d43bdf58027fb5770b6d611ae68a9f36b93ce5a50f874744ef1d7b72a11ad685f24081511c306fd7118dc60c90bdf08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T02:30:57Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "195c327842ae9601900016a50d0536a05136bfbf55ddb70657427ae12ed52181",
+ "sha512": "8deed35dcd9d634fcce94c42049829078c15a57d35ee83b15c201b6a0c80111fefba49cca522dedb1d79bc000393bf6c45b72d485f9d027f7c5a3f8922734a5c"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/hash.txt b/client/testdata/Published1Time_client_root_only/hash.txt
new file mode 100644
index 0000000..a03443e
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/hash.txt
@@ -0,0 +1 @@
+66b8896b5d1120fa684474d4d4d1a5438539b708c7a8d7a93682ce2fe0f739a4 \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.root.json b/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.snapshot.json b/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.targets.json b/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.timestamp.json b/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata.staged/root.json b/client/testdata/Published1Time_client_root_only/server/metadata.staged/root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata.staged/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata.staged/snapshot.json b/client/testdata/Published1Time_client_root_only/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata.staged/targets.json b/client/testdata/Published1Time_client_root_only/server/metadata.staged/targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata.staged/timestamp.json b/client/testdata/Published1Time_client_root_only/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata/1.root.json b/client/testdata/Published1Time_client_root_only/server/metadata/1.root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata/1.snapshot.json b/client/testdata/Published1Time_client_root_only/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata/1.targets.json b/client/testdata/Published1Time_client_root_only/server/metadata/1.targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata/1.timestamp.json b/client/testdata/Published1Time_client_root_only/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata/root.json b/client/testdata/Published1Time_client_root_only/server/metadata/root.json
new file mode 100644
index 0000000..8c17422
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "05353c8d3ec99bf81adec84a3d825a93d0a2e2afde92cfd83397451cd3def63dbcbeca4b4c9873c46ec8f509351ca6f9413d3da6bf166f285be6865a05135709"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-14T12:40:00Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata/snapshot.json b/client/testdata/Published1Time_client_root_only/server/metadata/snapshot.json
new file mode 100644
index 0000000..09129ad
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "f4684f93d49f3e20c837ff97557fa46e86dc6267973e21baa770de4a9829677e2d54d24b931062077e6ec6920c61507cb9859d06d6b6dcaf33fb7b5706226d03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata/targets.json b/client/testdata/Published1Time_client_root_only/server/metadata/targets.json
new file mode 100644
index 0000000..61a5d6f
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "69ba74524d033175541495432ecb792395d1980b7218eaca5938a1be90fffbad9e84a64a96dc2029b1341e907187c6cc006be177022791925b04f062f6f2570b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-14T12:40:00Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published1Time_client_root_only/server/metadata/timestamp.json b/client/testdata/Published1Time_client_root_only/server/metadata/timestamp.json
new file mode 100644
index 0000000..9eff7d6
--- /dev/null
+++ b/client/testdata/Published1Time_client_root_only/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0a88915a0566feaa4df4a7f7a51e167ab4f8de8002de66046a6f6698ac2be2acfec915b87cb8b20cdff6e4e9a7c2a01dbb72bb6cc2906873de85c108dace8f02"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-14T12:40:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "dd1aa8bd502e5bdf982dcd41bfc6e8d3783b0cfa33be4bb60aff6da9a4491a5f",
+ "sha512": "338f427ab68a92c435e6c4e41a858b5e3481d57dd038eb35dbe1d2613d62f9aa1082187091d6f330e6c81de67fabac58e0d60c2bc2cbf2361932ae9f37c75705"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/current/1.root.json b/client/testdata/Published2Times_keyrotated/client/metadata/current/1.root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/current/1.snapshot.json b/client/testdata/Published2Times_keyrotated/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/current/1.targets.json b/client/testdata/Published2Times_keyrotated/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/current/1.timestamp.json b/client/testdata/Published2Times_keyrotated/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/current/root.json b/client/testdata/Published2Times_keyrotated/client/metadata/current/root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/current/snapshot.json b/client/testdata/Published2Times_keyrotated/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/current/targets.json b/client/testdata/Published2Times_keyrotated/client/metadata/current/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/current/timestamp.json b/client/testdata/Published2Times_keyrotated/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.root.json b/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.snapshot.json b/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.targets.json b/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.timestamp.json b/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/previous/root.json b/client/testdata/Published2Times_keyrotated/client/metadata/previous/root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/previous/snapshot.json b/client/testdata/Published2Times_keyrotated/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/previous/targets.json b/client/testdata/Published2Times_keyrotated/client/metadata/previous/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/client/metadata/previous/timestamp.json b/client/testdata/Published2Times_keyrotated/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/hash.txt b/client/testdata/Published2Times_keyrotated/hash.txt
new file mode 100644
index 0000000..94732d5
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/hash.txt
@@ -0,0 +1 @@
+46183e42ee757c6704e7a122ab225ca633157738809243e8bc3cd92c67699dab \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.root.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.snapshot.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.targets.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.timestamp.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/2.root.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/root.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/snapshot.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/targets.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata.staged/timestamp.json b/client/testdata/Published2Times_keyrotated/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/1.root.json b/client/testdata/Published2Times_keyrotated/server/metadata/1.root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/1.snapshot.json b/client/testdata/Published2Times_keyrotated/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/1.targets.json b/client/testdata/Published2Times_keyrotated/server/metadata/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/1.timestamp.json b/client/testdata/Published2Times_keyrotated/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/2.root.json b/client/testdata/Published2Times_keyrotated/server/metadata/2.root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/root.json b/client/testdata/Published2Times_keyrotated/server/metadata/root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/snapshot.json b/client/testdata/Published2Times_keyrotated/server/metadata/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/targets.json b/client/testdata/Published2Times_keyrotated/server/metadata/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated/server/metadata/timestamp.json b/client/testdata/Published2Times_keyrotated/server/metadata/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.snapshot.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.targets.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.timestamp.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/snapshot.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/targets.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/timestamp.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.snapshot.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.targets.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.timestamp.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/snapshot.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/targets.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/timestamp.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/hash.txt b/client/testdata/Published2Times_keyrotated_initialrootexpired/hash.txt
new file mode 100644
index 0000000..1c67393
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/hash.txt
@@ -0,0 +1 @@
+76108cb5fdef506ef1fb58b75b44008193d7368938b090f82deae5dc88dca7df \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.snapshot.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.targets.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.timestamp.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/2.root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/snapshot.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/targets.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/timestamp.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.snapshot.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.targets.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.timestamp.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/2.root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/2.root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/root.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/snapshot.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/targets.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/timestamp.json b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_initialrootexpired/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.root.json
new file mode 100644
index 0000000..4ab5da2
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "eb855bd4b5abba2553aeeedf6aa7df3a9823486e5ebdd5e584e465db4772872ba07e5d246260249853b8c5c5a057b59bb5f169b944691ec4d898293f3d1fb10e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.snapshot.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..2c24917
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7283800e4ad40d7461b03bf6cd8a37218d7a233341cd9fe803398902274b1647213567ee0287c852b33d1c157461cb999ff399e4dc293a07141afd6abea25a03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2021-09-17T04:00:47Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.targets.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..9d27a59
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6202bb047ad7e5a13b514be1b8c128621374a70c17dba57b29ed58691b0badfe4827e47b6de8ee6fa35b731d1be994a492aa60ba872ed7e9a332a1fbb1cf910c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2021-12-10T11:27:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.timestamp.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..99bdb51
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "f95d84c5d292a024dffc394b842fef1634d00491762841715083c167eb8be1cb5b34604c13d27a6b27a2b73e0188df5e89b219022038b22c8b6d493fdd13a906"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2021-09-11T04:00:47Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "a9591bd41f7ce1b3ce8a13c4266d7ad5ea91ad51279955d1965259aa7fd32394",
+ "sha512": "191cd66f1e3ebc512b186e3de6f15a1f36cb6a91a7a229f2df67161dd1e75d005a05cf2017512903525160f1c867e6a06b1bacc1ba8d3cdd7521bc9f46a998b3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/root.json
new file mode 100644
index 0000000..4ab5da2
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "eb855bd4b5abba2553aeeedf6aa7df3a9823486e5ebdd5e584e465db4772872ba07e5d246260249853b8c5c5a057b59bb5f169b944691ec4d898293f3d1fb10e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/snapshot.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..2c24917
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7283800e4ad40d7461b03bf6cd8a37218d7a233341cd9fe803398902274b1647213567ee0287c852b33d1c157461cb999ff399e4dc293a07141afd6abea25a03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2021-09-17T04:00:47Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/targets.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/targets.json
new file mode 100644
index 0000000..9d27a59
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6202bb047ad7e5a13b514be1b8c128621374a70c17dba57b29ed58691b0badfe4827e47b6de8ee6fa35b731d1be994a492aa60ba872ed7e9a332a1fbb1cf910c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2021-12-10T11:27:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/timestamp.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..99bdb51
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "f95d84c5d292a024dffc394b842fef1634d00491762841715083c167eb8be1cb5b34604c13d27a6b27a2b73e0188df5e89b219022038b22c8b6d493fdd13a906"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2021-09-11T04:00:47Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "a9591bd41f7ce1b3ce8a13c4266d7ad5ea91ad51279955d1965259aa7fd32394",
+ "sha512": "191cd66f1e3ebc512b186e3de6f15a1f36cb6a91a7a229f2df67161dd1e75d005a05cf2017512903525160f1c867e6a06b1bacc1ba8d3cdd7521bc9f46a998b3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..4ab5da2
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "eb855bd4b5abba2553aeeedf6aa7df3a9823486e5ebdd5e584e465db4772872ba07e5d246260249853b8c5c5a057b59bb5f169b944691ec4d898293f3d1fb10e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.snapshot.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..2c24917
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7283800e4ad40d7461b03bf6cd8a37218d7a233341cd9fe803398902274b1647213567ee0287c852b33d1c157461cb999ff399e4dc293a07141afd6abea25a03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2021-09-17T04:00:47Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.targets.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..9d27a59
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6202bb047ad7e5a13b514be1b8c128621374a70c17dba57b29ed58691b0badfe4827e47b6de8ee6fa35b731d1be994a492aa60ba872ed7e9a332a1fbb1cf910c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2021-12-10T11:27:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.timestamp.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..99bdb51
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "f95d84c5d292a024dffc394b842fef1634d00491762841715083c167eb8be1cb5b34604c13d27a6b27a2b73e0188df5e89b219022038b22c8b6d493fdd13a906"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2021-09-11T04:00:47Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "a9591bd41f7ce1b3ce8a13c4266d7ad5ea91ad51279955d1965259aa7fd32394",
+ "sha512": "191cd66f1e3ebc512b186e3de6f15a1f36cb6a91a7a229f2df67161dd1e75d005a05cf2017512903525160f1c867e6a06b1bacc1ba8d3cdd7521bc9f46a998b3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/root.json
new file mode 100644
index 0000000..4ab5da2
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "eb855bd4b5abba2553aeeedf6aa7df3a9823486e5ebdd5e584e465db4772872ba07e5d246260249853b8c5c5a057b59bb5f169b944691ec4d898293f3d1fb10e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/snapshot.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..2c24917
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7283800e4ad40d7461b03bf6cd8a37218d7a233341cd9fe803398902274b1647213567ee0287c852b33d1c157461cb999ff399e4dc293a07141afd6abea25a03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2021-09-17T04:00:47Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/targets.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/targets.json
new file mode 100644
index 0000000..9d27a59
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6202bb047ad7e5a13b514be1b8c128621374a70c17dba57b29ed58691b0badfe4827e47b6de8ee6fa35b731d1be994a492aa60ba872ed7e9a332a1fbb1cf910c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2021-12-10T11:27:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/timestamp.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..99bdb51
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "f95d84c5d292a024dffc394b842fef1634d00491762841715083c167eb8be1cb5b34604c13d27a6b27a2b73e0188df5e89b219022038b22c8b6d493fdd13a906"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2021-09-11T04:00:47Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "a9591bd41f7ce1b3ce8a13c4266d7ad5ea91ad51279955d1965259aa7fd32394",
+ "sha512": "191cd66f1e3ebc512b186e3de6f15a1f36cb6a91a7a229f2df67161dd1e75d005a05cf2017512903525160f1c867e6a06b1bacc1ba8d3cdd7521bc9f46a998b3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/hash.txt b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/hash.txt
new file mode 100644
index 0000000..33e50c5
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/hash.txt
@@ -0,0 +1 @@
+01bad68cbfe77f8d25005708a5ae983304afd54ef92062b4c75cedaf905ec102 \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..4ab5da2
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "eb855bd4b5abba2553aeeedf6aa7df3a9823486e5ebdd5e584e465db4772872ba07e5d246260249853b8c5c5a057b59bb5f169b944691ec4d898293f3d1fb10e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.snapshot.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..2c24917
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7283800e4ad40d7461b03bf6cd8a37218d7a233341cd9fe803398902274b1647213567ee0287c852b33d1c157461cb999ff399e4dc293a07141afd6abea25a03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2021-09-17T04:00:47Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.targets.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..9d27a59
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6202bb047ad7e5a13b514be1b8c128621374a70c17dba57b29ed58691b0badfe4827e47b6de8ee6fa35b731d1be994a492aa60ba872ed7e9a332a1fbb1cf910c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2021-12-10T11:27:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.timestamp.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..99bdb51
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "f95d84c5d292a024dffc394b842fef1634d00491762841715083c167eb8be1cb5b34604c13d27a6b27a2b73e0188df5e89b219022038b22c8b6d493fdd13a906"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2021-09-11T04:00:47Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "a9591bd41f7ce1b3ce8a13c4266d7ad5ea91ad51279955d1965259aa7fd32394",
+ "sha512": "191cd66f1e3ebc512b186e3de6f15a1f36cb6a91a7a229f2df67161dd1e75d005a05cf2017512903525160f1c867e6a06b1bacc1ba8d3cdd7521bc9f46a998b3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/2.root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..7977840
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "da7ef701db2051465c600eae99a3664653d298e6c6ce230b10414b7044ce18b98f17f15f05ecf5678ce776b4bab058db6aa0d920a832bd5d3c167e81d9d1160d"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "d1c36ce282376aeec81a74229e8d0927f87d0044aace710b93c11088e49a6889f6135dc18fafc23ea2caead0a9164bfec2c0f825d6979fb459ec6bf3471cdb0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/root.json
new file mode 100644
index 0000000..7977840
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "da7ef701db2051465c600eae99a3664653d298e6c6ce230b10414b7044ce18b98f17f15f05ecf5678ce776b4bab058db6aa0d920a832bd5d3c167e81d9d1160d"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "d1c36ce282376aeec81a74229e8d0927f87d0044aace710b93c11088e49a6889f6135dc18fafc23ea2caead0a9164bfec2c0f825d6979fb459ec6bf3471cdb0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/snapshot.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..2c24917
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7283800e4ad40d7461b03bf6cd8a37218d7a233341cd9fe803398902274b1647213567ee0287c852b33d1c157461cb999ff399e4dc293a07141afd6abea25a03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2021-09-17T04:00:47Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/targets.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/targets.json
new file mode 100644
index 0000000..9d27a59
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6202bb047ad7e5a13b514be1b8c128621374a70c17dba57b29ed58691b0badfe4827e47b6de8ee6fa35b731d1be994a492aa60ba872ed7e9a332a1fbb1cf910c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2021-12-10T11:27:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/timestamp.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..99bdb51
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "f95d84c5d292a024dffc394b842fef1634d00491762841715083c167eb8be1cb5b34604c13d27a6b27a2b73e0188df5e89b219022038b22c8b6d493fdd13a906"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2021-09-11T04:00:47Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "a9591bd41f7ce1b3ce8a13c4266d7ad5ea91ad51279955d1965259aa7fd32394",
+ "sha512": "191cd66f1e3ebc512b186e3de6f15a1f36cb6a91a7a229f2df67161dd1e75d005a05cf2017512903525160f1c867e6a06b1bacc1ba8d3cdd7521bc9f46a998b3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.root.json
new file mode 100644
index 0000000..4ab5da2
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "eb855bd4b5abba2553aeeedf6aa7df3a9823486e5ebdd5e584e465db4772872ba07e5d246260249853b8c5c5a057b59bb5f169b944691ec4d898293f3d1fb10e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.snapshot.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..2c24917
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7283800e4ad40d7461b03bf6cd8a37218d7a233341cd9fe803398902274b1647213567ee0287c852b33d1c157461cb999ff399e4dc293a07141afd6abea25a03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2021-09-17T04:00:47Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.targets.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.targets.json
new file mode 100644
index 0000000..9d27a59
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6202bb047ad7e5a13b514be1b8c128621374a70c17dba57b29ed58691b0badfe4827e47b6de8ee6fa35b731d1be994a492aa60ba872ed7e9a332a1fbb1cf910c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2021-12-10T11:27:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.timestamp.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..99bdb51
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "f95d84c5d292a024dffc394b842fef1634d00491762841715083c167eb8be1cb5b34604c13d27a6b27a2b73e0188df5e89b219022038b22c8b6d493fdd13a906"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2021-09-11T04:00:47Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "a9591bd41f7ce1b3ce8a13c4266d7ad5ea91ad51279955d1965259aa7fd32394",
+ "sha512": "191cd66f1e3ebc512b186e3de6f15a1f36cb6a91a7a229f2df67161dd1e75d005a05cf2017512903525160f1c867e6a06b1bacc1ba8d3cdd7521bc9f46a998b3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/2.root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/2.root.json
new file mode 100644
index 0000000..77fa70e
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "da7ef701db2051465c600eae99a3664653d298e6c6ce230b10414b7044ce18b98f17f15f05ecf5678ce776b4bab058db6aa0d920a832bd5d3c167e81d9d1160d"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "000000"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/root.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/root.json
new file mode 100644
index 0000000..7977840
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "da7ef701db2051465c600eae99a3664653d298e6c6ce230b10414b7044ce18b98f17f15f05ecf5678ce776b4bab058db6aa0d920a832bd5d3c167e81d9d1160d"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "d1c36ce282376aeec81a74229e8d0927f87d0044aace710b93c11088e49a6889f6135dc18fafc23ea2caead0a9164bfec2c0f825d6979fb459ec6bf3471cdb0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2022-09-10T09:49:07Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/snapshot.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/snapshot.json
new file mode 100644
index 0000000..2c24917
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7283800e4ad40d7461b03bf6cd8a37218d7a233341cd9fe803398902274b1647213567ee0287c852b33d1c157461cb999ff399e4dc293a07141afd6abea25a03"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2021-09-17T04:00:47Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/targets.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/targets.json
new file mode 100644
index 0000000..9d27a59
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6202bb047ad7e5a13b514be1b8c128621374a70c17dba57b29ed58691b0badfe4827e47b6de8ee6fa35b731d1be994a492aa60ba872ed7e9a332a1fbb1cf910c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2021-12-10T11:27:57Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/timestamp.json b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/timestamp.json
new file mode 100644
index 0000000..99bdb51
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidNewRootSignature/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "f95d84c5d292a024dffc394b842fef1634d00491762841715083c167eb8be1cb5b34604c13d27a6b27a2b73e0188df5e89b219022038b22c8b6d493fdd13a906"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2021-09-11T04:00:47Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "a9591bd41f7ce1b3ce8a13c4266d7ad5ea91ad51279955d1965259aa7fd32394",
+ "sha512": "191cd66f1e3ebc512b186e3de6f15a1f36cb6a91a7a229f2df67161dd1e75d005a05cf2017512903525160f1c867e6a06b1bacc1ba8d3cdd7521bc9f46a998b3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.root.json
new file mode 100644
index 0000000..b7fa821
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e3b2b8cbc279c332ef1f74f940235d7ab98e49cbbf09f78a4445285dc45bab991ecf98e714487f8e43bbd715efcbbff1d5ba91c190ba6a1826f0d6564aee6d05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.snapshot.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.targets.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.timestamp.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/root.json
new file mode 100644
index 0000000..b7fa821
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e3b2b8cbc279c332ef1f74f940235d7ab98e49cbbf09f78a4445285dc45bab991ecf98e714487f8e43bbd715efcbbff1d5ba91c190ba6a1826f0d6564aee6d05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/snapshot.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/targets.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/timestamp.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..b7fa821
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e3b2b8cbc279c332ef1f74f940235d7ab98e49cbbf09f78a4445285dc45bab991ecf98e714487f8e43bbd715efcbbff1d5ba91c190ba6a1826f0d6564aee6d05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.snapshot.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.targets.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.timestamp.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/root.json
new file mode 100644
index 0000000..b7fa821
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e3b2b8cbc279c332ef1f74f940235d7ab98e49cbbf09f78a4445285dc45bab991ecf98e714487f8e43bbd715efcbbff1d5ba91c190ba6a1826f0d6564aee6d05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/snapshot.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/targets.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/timestamp.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/hash.txt b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/hash.txt
new file mode 100644
index 0000000..1ffc9a6
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/hash.txt
@@ -0,0 +1 @@
+22e59be74a4c783467e0adc5a888fb7222ea10473baad417dbf64bd8fa220e5b \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..b7fa821
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e3b2b8cbc279c332ef1f74f940235d7ab98e49cbbf09f78a4445285dc45bab991ecf98e714487f8e43bbd715efcbbff1d5ba91c190ba6a1826f0d6564aee6d05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.snapshot.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.targets.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.timestamp.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/2.root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..ffe63b5
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "a730b0d31a67712f4bcda4406dd26d515717ce214c7768556a5ebbd9d4f0b8507eb6754421c7d46cabe469ab3db81a774055dfdee88457203bb1148576854e0a"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "4ba4216f3f0fcb14a39ff0690b48200a0b9b4bb3505433a1eaf8269e9eb73c78f36fa8b777fe69b3543b86845739ced2efe13f4d669724f6d6d2111b7dfe5c05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/root.json
new file mode 100644
index 0000000..ffe63b5
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "a730b0d31a67712f4bcda4406dd26d515717ce214c7768556a5ebbd9d4f0b8507eb6754421c7d46cabe469ab3db81a774055dfdee88457203bb1148576854e0a"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "4ba4216f3f0fcb14a39ff0690b48200a0b9b4bb3505433a1eaf8269e9eb73c78f36fa8b777fe69b3543b86845739ced2efe13f4d669724f6d6d2111b7dfe5c05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/snapshot.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/targets.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/timestamp.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.root.json
new file mode 100644
index 0000000..b7fa821
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e3b2b8cbc279c332ef1f74f940235d7ab98e49cbbf09f78a4445285dc45bab991ecf98e714487f8e43bbd715efcbbff1d5ba91c190ba6a1826f0d6564aee6d05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.snapshot.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.targets.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.timestamp.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/2.root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/2.root.json
new file mode 100644
index 0000000..d713d81
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "000000"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "4ba4216f3f0fcb14a39ff0690b48200a0b9b4bb3505433a1eaf8269e9eb73c78f36fa8b777fe69b3543b86845739ced2efe13f4d669724f6d6d2111b7dfe5c05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/root.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/root.json
new file mode 100644
index 0000000..ffe63b5
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "a730b0d31a67712f4bcda4406dd26d515717ce214c7768556a5ebbd9d4f0b8507eb6754421c7d46cabe469ab3db81a774055dfdee88457203bb1148576854e0a"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "4ba4216f3f0fcb14a39ff0690b48200a0b9b4bb3505433a1eaf8269e9eb73c78f36fa8b777fe69b3543b86845739ced2efe13f4d669724f6d6d2111b7dfe5c05"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/snapshot.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/targets.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/timestamp.json b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published2Times_keyrotated_invalidOldRootSignature/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.root.json b/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.snapshot.json b/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.targets.json b/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.timestamp.json b/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/current/root.json b/client/testdata/Published2Times_roottoolarge/client/metadata/current/root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/current/snapshot.json b/client/testdata/Published2Times_roottoolarge/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/current/targets.json b/client/testdata/Published2Times_roottoolarge/client/metadata/current/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/current/timestamp.json b/client/testdata/Published2Times_roottoolarge/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.root.json b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.snapshot.json b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.targets.json b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.timestamp.json b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/previous/root.json b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/previous/snapshot.json b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/previous/targets.json b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/client/metadata/previous/timestamp.json b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/hash.txt b/client/testdata/Published2Times_roottoolarge/hash.txt
new file mode 100644
index 0000000..94732d5
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/hash.txt
@@ -0,0 +1 @@
+46183e42ee757c6704e7a122ab225ca633157738809243e8bc3cd92c67699dab \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.root.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.snapshot.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.targets.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.timestamp.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/2.root.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/root.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/snapshot.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/targets.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata.staged/timestamp.json b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/1.root.json b/client/testdata/Published2Times_roottoolarge/server/metadata/1.root.json
new file mode 100644
index 0000000..fff0394
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "481a1bd10fedbe33e88e5f586a8726f558a5ec426bf65d2ba1c449fd1f69148da6af40f1ae18a5be63539dfbf076019db8b28f3f644f7fd4f003b7d06a4c2e09"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/1.snapshot.json b/client/testdata/Published2Times_roottoolarge/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/1.targets.json b/client/testdata/Published2Times_roottoolarge/server/metadata/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/1.timestamp.json b/client/testdata/Published2Times_roottoolarge/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/2.root.json b/client/testdata/Published2Times_roottoolarge/server/metadata/2.root.json
new file mode 100644
index 0000000..4aad61d
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/2.root.json
Binary files differ
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/root.json b/client/testdata/Published2Times_roottoolarge/server/metadata/root.json
new file mode 100644
index 0000000..21d082c
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "122b322d6f212fcc22ec9062e6948baf694ec4ecf366e4a3cd5cf4c738aa39706b3420aeacd6d8e0e54f0af0168644e905696b397974dadfd3d2a578be06fd08"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c5e5a542c368d32a8bb1884ffcc850f8902c96537b2b827be3608c44dbdd24ff0cd3b0c326e03869474091e21073f87a57237f2909300a2c43a41ca90c22020c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/snapshot.json b/client/testdata/Published2Times_roottoolarge/server/metadata/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/targets.json b/client/testdata/Published2Times_roottoolarge/server/metadata/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_roottoolarge/server/metadata/timestamp.json b/client/testdata/Published2Times_roottoolarge/server/metadata/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published2Times_roottoolarge/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.root.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.targets.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/root.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/targets.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.root.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.targets.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/root.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/targets.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/hash.txt b/client/testdata/Published2Times_snapshot_keyrotated/hash.txt
new file mode 100644
index 0000000..605aae2
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/hash.txt
@@ -0,0 +1 @@
+c8b65c989faaae06e6962fd58f0d2d84ae5b3786caa60b80514e23877a0ec1a4 \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.root.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.targets.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.root.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..e829eb1
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "b5ee398bcfdeffec3bc84420a9f21282543e7a45cbd8ffa896b0e3d99d614551fa72e387fd809306ddc1454e16fe8edb01b5808d16f186b664a538353bd38802"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.snapshot.json
new file mode 100644
index 0000000..850e763
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "ba835d8c4556c0d490e81a0aae287dd7359ffa2378160161979756b8c7e04d492eb370c50447c746064e38b318323059168fa319de2399da8387aeb7f951f401"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d9617e7899227aa417a59db0ecd64775fc651cbb3c4196fb45c36885d8bce0af9e19f2e9b0ce1e2f089b72a8bf7d120fdafe649c1ce79b1e0c95845b541c6805"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.timestamp.json
new file mode 100644
index 0000000..d902a0b
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/2.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "57e25d5c42c25aeda1cfa5e6144567661867d15ef35746fedb685ea077e2dc803091ab26b3e50dfeedf405454f38159e453541a518c80a9c10923eff240fee07"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "70122787e9fa61099a44d5bc04b4b52c95a6eb553ba7e60b7b778b5d1539913c",
+ "sha512": "2290f310345240bcba820722f09d70a0a463032c8c807122e30385a7e6a9fbd3ff044dce5d0994d182853ba1651057920ebbf0447c470d85370fb57afef9034e"
+ },
+ "length": 661,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/root.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/root.json
new file mode 100644
index 0000000..e829eb1
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "b5ee398bcfdeffec3bc84420a9f21282543e7a45cbd8ffa896b0e3d99d614551fa72e387fd809306ddc1454e16fe8edb01b5808d16f186b664a538353bd38802"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..850e763
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "ba835d8c4556c0d490e81a0aae287dd7359ffa2378160161979756b8c7e04d492eb370c50447c746064e38b318323059168fa319de2399da8387aeb7f951f401"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d9617e7899227aa417a59db0ecd64775fc651cbb3c4196fb45c36885d8bce0af9e19f2e9b0ce1e2f089b72a8bf7d120fdafe649c1ce79b1e0c95845b541c6805"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/targets.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..d902a0b
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "57e25d5c42c25aeda1cfa5e6144567661867d15ef35746fedb685ea077e2dc803091ab26b3e50dfeedf405454f38159e453541a518c80a9c10923eff240fee07"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "70122787e9fa61099a44d5bc04b4b52c95a6eb553ba7e60b7b778b5d1539913c",
+ "sha512": "2290f310345240bcba820722f09d70a0a463032c8c807122e30385a7e6a9fbd3ff044dce5d0994d182853ba1651057920ebbf0447c470d85370fb57afef9034e"
+ },
+ "length": 661,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.root.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.targets.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.root.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.root.json
new file mode 100644
index 0000000..e829eb1
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "b5ee398bcfdeffec3bc84420a9f21282543e7a45cbd8ffa896b0e3d99d614551fa72e387fd809306ddc1454e16fe8edb01b5808d16f186b664a538353bd38802"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.snapshot.json
new file mode 100644
index 0000000..850e763
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "ba835d8c4556c0d490e81a0aae287dd7359ffa2378160161979756b8c7e04d492eb370c50447c746064e38b318323059168fa319de2399da8387aeb7f951f401"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d9617e7899227aa417a59db0ecd64775fc651cbb3c4196fb45c36885d8bce0af9e19f2e9b0ce1e2f089b72a8bf7d120fdafe649c1ce79b1e0c95845b541c6805"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.timestamp.json
new file mode 100644
index 0000000..d902a0b
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/2.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "57e25d5c42c25aeda1cfa5e6144567661867d15ef35746fedb685ea077e2dc803091ab26b3e50dfeedf405454f38159e453541a518c80a9c10923eff240fee07"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "70122787e9fa61099a44d5bc04b4b52c95a6eb553ba7e60b7b778b5d1539913c",
+ "sha512": "2290f310345240bcba820722f09d70a0a463032c8c807122e30385a7e6a9fbd3ff044dce5d0994d182853ba1651057920ebbf0447c470d85370fb57afef9034e"
+ },
+ "length": 661,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/root.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/root.json
new file mode 100644
index 0000000..e829eb1
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "b5ee398bcfdeffec3bc84420a9f21282543e7a45cbd8ffa896b0e3d99d614551fa72e387fd809306ddc1454e16fe8edb01b5808d16f186b664a538353bd38802"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/snapshot.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/snapshot.json
new file mode 100644
index 0000000..850e763
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "ba835d8c4556c0d490e81a0aae287dd7359ffa2378160161979756b8c7e04d492eb370c50447c746064e38b318323059168fa319de2399da8387aeb7f951f401"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d9617e7899227aa417a59db0ecd64775fc651cbb3c4196fb45c36885d8bce0af9e19f2e9b0ce1e2f089b72a8bf7d120fdafe649c1ce79b1e0c95845b541c6805"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/targets.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/timestamp.json b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/timestamp.json
new file mode 100644
index 0000000..d902a0b
--- /dev/null
+++ b/client/testdata/Published2Times_snapshot_keyrotated/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "57e25d5c42c25aeda1cfa5e6144567661867d15ef35746fedb685ea077e2dc803091ab26b3e50dfeedf405454f38159e453541a518c80a9c10923eff240fee07"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "70122787e9fa61099a44d5bc04b4b52c95a6eb553ba7e60b7b778b5d1539913c",
+ "sha512": "2290f310345240bcba820722f09d70a0a463032c8c807122e30385a7e6a9fbd3ff044dce5d0994d182853ba1651057920ebbf0447c470d85370fb57afef9034e"
+ },
+ "length": 661,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.root.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.root.json
new file mode 100644
index 0000000..2f7a7ac
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "178b16c8bfeefe6b9d2296be36a09ec8d0ec5e2ddcf7db851c1157c2da9ac78623736ddd0e145ef791c00370a261c47ff09eb29817d21a3bd02057b32ad93603"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.snapshot.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..580836d
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "aecb0a813124c7a42c125d94fa8dfd0bd6083acc30a1e8e2d85956d61cdd296f8adb561f8035826f9ad0480c5f3fd8581e0595ddf719a6b839acd9549667ad0d"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.targets.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..74c79a6
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "923e033c7952d24cb509f62d8dfed00fb554b9dd3578567ddd2205377038044a5dd45c132755fed5b3e5f5ff8e4204fa0d219b47bc0f39c1b48fad2fdbc94805"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.timestamp.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..62c7756
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "d284bf6f5dc45828cd3d3cc771657ade69731d815df34bb0eec14dd645aa235eb67c32bb5cb7ad1d4e56e69fc1df002bf48c7264c6f9105e05cbb9a2c6c36b0d"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "8c30184e206b001443cc8c5ab74a45fbb80e3b6f7937fd42917635b64970442e",
+ "sha512": "22f32f8178f842277f0e2e3dc24c03ccdf310be97533ddf41dffafa40be3448d9b22a1ac596c2b04b2357475823501156e52a2e9c39b730603f3c7ed3c8769d1"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/root.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/root.json
new file mode 100644
index 0000000..2f7a7ac
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "178b16c8bfeefe6b9d2296be36a09ec8d0ec5e2ddcf7db851c1157c2da9ac78623736ddd0e145ef791c00370a261c47ff09eb29817d21a3bd02057b32ad93603"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/snapshot.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..580836d
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "aecb0a813124c7a42c125d94fa8dfd0bd6083acc30a1e8e2d85956d61cdd296f8adb561f8035826f9ad0480c5f3fd8581e0595ddf719a6b839acd9549667ad0d"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/targets.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/targets.json
new file mode 100644
index 0000000..74c79a6
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "923e033c7952d24cb509f62d8dfed00fb554b9dd3578567ddd2205377038044a5dd45c132755fed5b3e5f5ff8e4204fa0d219b47bc0f39c1b48fad2fdbc94805"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/timestamp.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..62c7756
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "d284bf6f5dc45828cd3d3cc771657ade69731d815df34bb0eec14dd645aa235eb67c32bb5cb7ad1d4e56e69fc1df002bf48c7264c6f9105e05cbb9a2c6c36b0d"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "8c30184e206b001443cc8c5ab74a45fbb80e3b6f7937fd42917635b64970442e",
+ "sha512": "22f32f8178f842277f0e2e3dc24c03ccdf310be97533ddf41dffafa40be3448d9b22a1ac596c2b04b2357475823501156e52a2e9c39b730603f3c7ed3c8769d1"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.root.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..2f7a7ac
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "178b16c8bfeefe6b9d2296be36a09ec8d0ec5e2ddcf7db851c1157c2da9ac78623736ddd0e145ef791c00370a261c47ff09eb29817d21a3bd02057b32ad93603"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.snapshot.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..580836d
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "aecb0a813124c7a42c125d94fa8dfd0bd6083acc30a1e8e2d85956d61cdd296f8adb561f8035826f9ad0480c5f3fd8581e0595ddf719a6b839acd9549667ad0d"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.targets.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..74c79a6
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "923e033c7952d24cb509f62d8dfed00fb554b9dd3578567ddd2205377038044a5dd45c132755fed5b3e5f5ff8e4204fa0d219b47bc0f39c1b48fad2fdbc94805"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.timestamp.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..62c7756
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "d284bf6f5dc45828cd3d3cc771657ade69731d815df34bb0eec14dd645aa235eb67c32bb5cb7ad1d4e56e69fc1df002bf48c7264c6f9105e05cbb9a2c6c36b0d"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "8c30184e206b001443cc8c5ab74a45fbb80e3b6f7937fd42917635b64970442e",
+ "sha512": "22f32f8178f842277f0e2e3dc24c03ccdf310be97533ddf41dffafa40be3448d9b22a1ac596c2b04b2357475823501156e52a2e9c39b730603f3c7ed3c8769d1"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/root.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/root.json
new file mode 100644
index 0000000..2f7a7ac
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "178b16c8bfeefe6b9d2296be36a09ec8d0ec5e2ddcf7db851c1157c2da9ac78623736ddd0e145ef791c00370a261c47ff09eb29817d21a3bd02057b32ad93603"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/snapshot.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..580836d
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "aecb0a813124c7a42c125d94fa8dfd0bd6083acc30a1e8e2d85956d61cdd296f8adb561f8035826f9ad0480c5f3fd8581e0595ddf719a6b839acd9549667ad0d"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/targets.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/targets.json
new file mode 100644
index 0000000..74c79a6
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "923e033c7952d24cb509f62d8dfed00fb554b9dd3578567ddd2205377038044a5dd45c132755fed5b3e5f5ff8e4204fa0d219b47bc0f39c1b48fad2fdbc94805"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/timestamp.json b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..62c7756
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "d284bf6f5dc45828cd3d3cc771657ade69731d815df34bb0eec14dd645aa235eb67c32bb5cb7ad1d4e56e69fc1df002bf48c7264c6f9105e05cbb9a2c6c36b0d"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "8c30184e206b001443cc8c5ab74a45fbb80e3b6f7937fd42917635b64970442e",
+ "sha512": "22f32f8178f842277f0e2e3dc24c03ccdf310be97533ddf41dffafa40be3448d9b22a1ac596c2b04b2357475823501156e52a2e9c39b730603f3c7ed3c8769d1"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/hash.txt b/client/testdata/Published2Times_targets_keyrotated/hash.txt
new file mode 100644
index 0000000..6032a36
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/hash.txt
@@ -0,0 +1 @@
+da386545caf1b4bc294d8be949b3bd1a81de1d72cbeb9389c678e5ded14de472 \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.root.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..2f7a7ac
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "178b16c8bfeefe6b9d2296be36a09ec8d0ec5e2ddcf7db851c1157c2da9ac78623736ddd0e145ef791c00370a261c47ff09eb29817d21a3bd02057b32ad93603"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.snapshot.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..580836d
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "aecb0a813124c7a42c125d94fa8dfd0bd6083acc30a1e8e2d85956d61cdd296f8adb561f8035826f9ad0480c5f3fd8581e0595ddf719a6b839acd9549667ad0d"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.targets.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..74c79a6
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "923e033c7952d24cb509f62d8dfed00fb554b9dd3578567ddd2205377038044a5dd45c132755fed5b3e5f5ff8e4204fa0d219b47bc0f39c1b48fad2fdbc94805"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.timestamp.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..62c7756
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "d284bf6f5dc45828cd3d3cc771657ade69731d815df34bb0eec14dd645aa235eb67c32bb5cb7ad1d4e56e69fc1df002bf48c7264c6f9105e05cbb9a2c6c36b0d"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "8c30184e206b001443cc8c5ab74a45fbb80e3b6f7937fd42917635b64970442e",
+ "sha512": "22f32f8178f842277f0e2e3dc24c03ccdf310be97533ddf41dffafa40be3448d9b22a1ac596c2b04b2357475823501156e52a2e9c39b730603f3c7ed3c8769d1"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.root.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..8d2460c
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "11c2da5dc46bcf9e2aa41ce633778480a5f74160c2547085dd85b802e67b71c10e2a3fd2b1fcf800364408a555ca2e1f309cf09137b1225bc015f9535e2c5a01"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.snapshot.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.snapshot.json
new file mode 100644
index 0000000..5e10456
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "faa00b87121a1cefdaec6ea5fe1078465d989ac73b18220b594dc62302d8c661d6dea53f9baea9c8fc29fb988c889d5096a9914979ad53027cd281de1549f107"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.targets.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.targets.json
new file mode 100644
index 0000000..91de6b5
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.targets.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9d7c09bf862a443be7a1cf14c1d2bf2f812dea9ab33567f14a3b33ff52af70865fd11c17ed92d187bb319fd984709a6ed59cc3d216ee9f3151af0764eafa460d"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "94541820374b4a4a7d92a8d7fbf22ab5e203c1da0559c21e1168f2ebcbcf19a939bf46f4c96cf678480708ff5549f14fec56e3163cce60f4e753f173ebbc6706"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.timestamp.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.timestamp.json
new file mode 100644
index 0000000..ff74b84
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/2.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "56e9600e8db24012f69219ecb7d27edb0c4e3da64dabccd6948c4c12e42be4360854941ced0f6b11544166306c225ed9cd7b2e979de11340246890ed664a3707"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "26e36995257b9d61a9e7d06fe5c7ce43c1f01db1d078509ac1ca91c7a3ea6b97",
+ "sha512": "ba915258f9b6f711809e657aef8c37f76009be392fe560da3af6ee49e9027101d9d27c8d6cc74502947445fe59864502c992374c5c68dfe5f91a35c72a49d6df"
+ },
+ "length": 431,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/root.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/root.json
new file mode 100644
index 0000000..8d2460c
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "11c2da5dc46bcf9e2aa41ce633778480a5f74160c2547085dd85b802e67b71c10e2a3fd2b1fcf800364408a555ca2e1f309cf09137b1225bc015f9535e2c5a01"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/snapshot.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..5e10456
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "faa00b87121a1cefdaec6ea5fe1078465d989ac73b18220b594dc62302d8c661d6dea53f9baea9c8fc29fb988c889d5096a9914979ad53027cd281de1549f107"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/targets.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/targets.json
new file mode 100644
index 0000000..91de6b5
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/targets.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9d7c09bf862a443be7a1cf14c1d2bf2f812dea9ab33567f14a3b33ff52af70865fd11c17ed92d187bb319fd984709a6ed59cc3d216ee9f3151af0764eafa460d"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "94541820374b4a4a7d92a8d7fbf22ab5e203c1da0559c21e1168f2ebcbcf19a939bf46f4c96cf678480708ff5549f14fec56e3163cce60f4e753f173ebbc6706"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/timestamp.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..ff74b84
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "56e9600e8db24012f69219ecb7d27edb0c4e3da64dabccd6948c4c12e42be4360854941ced0f6b11544166306c225ed9cd7b2e979de11340246890ed664a3707"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "26e36995257b9d61a9e7d06fe5c7ce43c1f01db1d078509ac1ca91c7a3ea6b97",
+ "sha512": "ba915258f9b6f711809e657aef8c37f76009be392fe560da3af6ee49e9027101d9d27c8d6cc74502947445fe59864502c992374c5c68dfe5f91a35c72a49d6df"
+ },
+ "length": 431,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.root.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.root.json
new file mode 100644
index 0000000..2f7a7ac
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "178b16c8bfeefe6b9d2296be36a09ec8d0ec5e2ddcf7db851c1157c2da9ac78623736ddd0e145ef791c00370a261c47ff09eb29817d21a3bd02057b32ad93603"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.snapshot.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..580836d
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "aecb0a813124c7a42c125d94fa8dfd0bd6083acc30a1e8e2d85956d61cdd296f8adb561f8035826f9ad0480c5f3fd8581e0595ddf719a6b839acd9549667ad0d"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.targets.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.targets.json
new file mode 100644
index 0000000..74c79a6
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "923e033c7952d24cb509f62d8dfed00fb554b9dd3578567ddd2205377038044a5dd45c132755fed5b3e5f5ff8e4204fa0d219b47bc0f39c1b48fad2fdbc94805"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.timestamp.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..62c7756
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "d284bf6f5dc45828cd3d3cc771657ade69731d815df34bb0eec14dd645aa235eb67c32bb5cb7ad1d4e56e69fc1df002bf48c7264c6f9105e05cbb9a2c6c36b0d"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "8c30184e206b001443cc8c5ab74a45fbb80e3b6f7937fd42917635b64970442e",
+ "sha512": "22f32f8178f842277f0e2e3dc24c03ccdf310be97533ddf41dffafa40be3448d9b22a1ac596c2b04b2357475823501156e52a2e9c39b730603f3c7ed3c8769d1"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.root.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.root.json
new file mode 100644
index 0000000..8d2460c
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "11c2da5dc46bcf9e2aa41ce633778480a5f74160c2547085dd85b802e67b71c10e2a3fd2b1fcf800364408a555ca2e1f309cf09137b1225bc015f9535e2c5a01"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.snapshot.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.snapshot.json
new file mode 100644
index 0000000..5e10456
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "faa00b87121a1cefdaec6ea5fe1078465d989ac73b18220b594dc62302d8c661d6dea53f9baea9c8fc29fb988c889d5096a9914979ad53027cd281de1549f107"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.targets.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.targets.json
new file mode 100644
index 0000000..91de6b5
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.targets.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9d7c09bf862a443be7a1cf14c1d2bf2f812dea9ab33567f14a3b33ff52af70865fd11c17ed92d187bb319fd984709a6ed59cc3d216ee9f3151af0764eafa460d"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "94541820374b4a4a7d92a8d7fbf22ab5e203c1da0559c21e1168f2ebcbcf19a939bf46f4c96cf678480708ff5549f14fec56e3163cce60f4e753f173ebbc6706"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.timestamp.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.timestamp.json
new file mode 100644
index 0000000..ff74b84
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/2.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "56e9600e8db24012f69219ecb7d27edb0c4e3da64dabccd6948c4c12e42be4360854941ced0f6b11544166306c225ed9cd7b2e979de11340246890ed664a3707"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "26e36995257b9d61a9e7d06fe5c7ce43c1f01db1d078509ac1ca91c7a3ea6b97",
+ "sha512": "ba915258f9b6f711809e657aef8c37f76009be392fe560da3af6ee49e9027101d9d27c8d6cc74502947445fe59864502c992374c5c68dfe5f91a35c72a49d6df"
+ },
+ "length": 431,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/root.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/root.json
new file mode 100644
index 0000000..8d2460c
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "11c2da5dc46bcf9e2aa41ce633778480a5f74160c2547085dd85b802e67b71c10e2a3fd2b1fcf800364408a555ca2e1f309cf09137b1225bc015f9535e2c5a01"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:40Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/snapshot.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/snapshot.json
new file mode 100644
index 0000000..5e10456
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "faa00b87121a1cefdaec6ea5fe1078465d989ac73b18220b594dc62302d8c661d6dea53f9baea9c8fc29fb988c889d5096a9914979ad53027cd281de1549f107"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "targets.json": {
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/targets.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/targets.json
new file mode 100644
index 0000000..91de6b5
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/targets.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9d7c09bf862a443be7a1cf14c1d2bf2f812dea9ab33567f14a3b33ff52af70865fd11c17ed92d187bb319fd984709a6ed59cc3d216ee9f3151af0764eafa460d"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "94541820374b4a4a7d92a8d7fbf22ab5e203c1da0559c21e1168f2ebcbcf19a939bf46f4c96cf678480708ff5549f14fec56e3163cce60f4e753f173ebbc6706"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:40Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_targets_keyrotated/server/metadata/timestamp.json b/client/testdata/Published2Times_targets_keyrotated/server/metadata/timestamp.json
new file mode 100644
index 0000000..ff74b84
--- /dev/null
+++ b/client/testdata/Published2Times_targets_keyrotated/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "56e9600e8db24012f69219ecb7d27edb0c4e3da64dabccd6948c4c12e42be4360854941ced0f6b11544166306c225ed9cd7b2e979de11340246890ed664a3707"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:40Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "26e36995257b9d61a9e7d06fe5c7ce43c1f01db1d078509ac1ca91c7a3ea6b97",
+ "sha512": "ba915258f9b6f711809e657aef8c37f76009be392fe560da3af6ee49e9027101d9d27c8d6cc74502947445fe59864502c992374c5c68dfe5f91a35c72a49d6df"
+ },
+ "length": 431,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.root.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.snapshot.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.targets.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/root.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/snapshot.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/targets.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.root.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.snapshot.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.targets.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/root.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/snapshot.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/targets.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/hash.txt b/client/testdata/Published2Times_timestamp_keyrotated/hash.txt
new file mode 100644
index 0000000..68ccd58
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/hash.txt
@@ -0,0 +1 @@
+cc8aeb813bb72f4057f56f8c89fc81548ad290592fb0023df691ce505e402a57 \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.root.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.snapshot.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.targets.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/2.root.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..1d316a8
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/2.root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "751ae9cf8ef0167e182eae200b05a236041b1a2c4e5fa599c304bf7f99a6f1dcb1627dce10f792dbe85481921e548e4edae2567ec41a927affa3b25d0bb3b804"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/2.timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/2.timestamp.json
new file mode 100644
index 0000000..cf81540
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/2.timestamp.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "955181a388d83f9ce358bd5430379b66fa3e65412838e807ded2ef4ac0a9c191a0bdceb572b607b923d7ffbfda9c83c621bc8cc9644a8496730b436e7b332101"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "388320f4ba609a57b74687f5c6ec7b64cfbceeac6040631a93515d4f15021cc033518dfc170b979259f227b250063a377d89be042aec0222ef739b2582200a03"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/root.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/root.json
new file mode 100644
index 0000000..1d316a8
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "751ae9cf8ef0167e182eae200b05a236041b1a2c4e5fa599c304bf7f99a6f1dcb1627dce10f792dbe85481921e548e4edae2567ec41a927affa3b25d0bb3b804"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/snapshot.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/targets.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..cf81540
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata.staged/timestamp.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "955181a388d83f9ce358bd5430379b66fa3e65412838e807ded2ef4ac0a9c191a0bdceb572b607b923d7ffbfda9c83c621bc8cc9644a8496730b436e7b332101"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "388320f4ba609a57b74687f5c6ec7b64cfbceeac6040631a93515d4f15021cc033518dfc170b979259f227b250063a377d89be042aec0222ef739b2582200a03"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.root.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.snapshot.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.targets.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/2.root.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/2.root.json
new file mode 100644
index 0000000..1d316a8
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/2.root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "751ae9cf8ef0167e182eae200b05a236041b1a2c4e5fa599c304bf7f99a6f1dcb1627dce10f792dbe85481921e548e4edae2567ec41a927affa3b25d0bb3b804"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/2.timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/2.timestamp.json
new file mode 100644
index 0000000..cf81540
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/2.timestamp.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "955181a388d83f9ce358bd5430379b66fa3e65412838e807ded2ef4ac0a9c191a0bdceb572b607b923d7ffbfda9c83c621bc8cc9644a8496730b436e7b332101"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "388320f4ba609a57b74687f5c6ec7b64cfbceeac6040631a93515d4f15021cc033518dfc170b979259f227b250063a377d89be042aec0222ef739b2582200a03"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/root.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/root.json
new file mode 100644
index 0000000..1d316a8
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/root.json
@@ -0,0 +1,99 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "751ae9cf8ef0167e182eae200b05a236041b1a2c4e5fa599c304bf7f99a6f1dcb1627dce10f792dbe85481921e548e4edae2567ec41a927affa3b25d0bb3b804"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/snapshot.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/targets.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/timestamp.json b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/timestamp.json
new file mode 100644
index 0000000..cf81540
--- /dev/null
+++ b/client/testdata/Published2Times_timestamp_keyrotated/server/metadata/timestamp.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "955181a388d83f9ce358bd5430379b66fa3e65412838e807ded2ef4ac0a9c191a0bdceb572b607b923d7ffbfda9c83c621bc8cc9644a8496730b436e7b332101"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "388320f4ba609a57b74687f5c6ec7b64cfbceeac6040631a93515d4f15021cc033518dfc170b979259f227b250063a377d89be042aec0222ef739b2582200a03"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.snapshot.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.targets.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.timestamp.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/snapshot.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/targets.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/timestamp.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.snapshot.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.targets.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.timestamp.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/snapshot.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/targets.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/timestamp.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/hash.txt b/client/testdata/Published3Times_keyrotated_forwardRootVersion/hash.txt
new file mode 100644
index 0000000..c3fe8ab
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/hash.txt
@@ -0,0 +1 @@
+31049291c7fd0a608a198abff1961a1c8ba43af52adc2db79c274959f2b985a0 \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.snapshot.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.targets.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.timestamp.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/2.root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..87c727f
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "954ba1348157a7f20cc63d59c3049545d31eee1934eee6db9393b29ce7be5b4497863d6559564fe900f313f03ef27acfb49df1df0e45f6585c8209c561c62f01"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "9a0818b253bd97827a036cde24325174ab93a7293e1326ab578ce8ca3c60c451f7a74757eb88796699e08a6247bbf73fe55a5e4f68d3ccf29bfd07703e905b07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/3.root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/3.root.json
new file mode 100644
index 0000000..320afc3
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/3.root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "779ac2132b1e22ad3a5893ed1ccb72517743a4827bd37218f4b4c95710c3a9ed5a127b1a82ee8bfbc68e19d31574610eb47b290f43b8229fc820de6a98575706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "ba5ed25669dbd59354848e288e6fda23b9a86a9e350137a5e6c8beb7a78dad48b148b89cc5cb8c7754e74b47dc66c419957cfe68cb5b2213477cd8b5b5bfe304"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "231490d1052f581842715786bf0692c61c75ef17f37b0dad9432d5464bf9e34f3740ddf103f7afc5bcaa8ade9c1fd310a6393a44de79be38d24e5bdd50190304"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/root.json
new file mode 100644
index 0000000..320afc3
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "779ac2132b1e22ad3a5893ed1ccb72517743a4827bd37218f4b4c95710c3a9ed5a127b1a82ee8bfbc68e19d31574610eb47b290f43b8229fc820de6a98575706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "ba5ed25669dbd59354848e288e6fda23b9a86a9e350137a5e6c8beb7a78dad48b148b89cc5cb8c7754e74b47dc66c419957cfe68cb5b2213477cd8b5b5bfe304"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "231490d1052f581842715786bf0692c61c75ef17f37b0dad9432d5464bf9e34f3740ddf103f7afc5bcaa8ade9c1fd310a6393a44de79be38d24e5bdd50190304"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/snapshot.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/targets.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/timestamp.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.root.json
new file mode 100644
index 0000000..7aa3dbd
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "7e2a5844a166b2873c86ee92fc2c07319424ddb66a0372797633f9dd5bcb869409eb00384095fd3e8f4fe5ca451da5ef6b3b864fc8c1b3bd1946743f30411205"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.snapshot.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.targets.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.timestamp.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/2.root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/2.root.json
new file mode 100644
index 0000000..320afc3
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/2.root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "779ac2132b1e22ad3a5893ed1ccb72517743a4827bd37218f4b4c95710c3a9ed5a127b1a82ee8bfbc68e19d31574610eb47b290f43b8229fc820de6a98575706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "ba5ed25669dbd59354848e288e6fda23b9a86a9e350137a5e6c8beb7a78dad48b148b89cc5cb8c7754e74b47dc66c419957cfe68cb5b2213477cd8b5b5bfe304"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "231490d1052f581842715786bf0692c61c75ef17f37b0dad9432d5464bf9e34f3740ddf103f7afc5bcaa8ade9c1fd310a6393a44de79be38d24e5bdd50190304"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/root.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/root.json
new file mode 100644
index 0000000..320afc3
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "779ac2132b1e22ad3a5893ed1ccb72517743a4827bd37218f4b4c95710c3a9ed5a127b1a82ee8bfbc68e19d31574610eb47b290f43b8229fc820de6a98575706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "ba5ed25669dbd59354848e288e6fda23b9a86a9e350137a5e6c8beb7a78dad48b148b89cc5cb8c7754e74b47dc66c419957cfe68cb5b2213477cd8b5b5bfe304"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "231490d1052f581842715786bf0692c61c75ef17f37b0dad9432d5464bf9e34f3740ddf103f7afc5bcaa8ade9c1fd310a6393a44de79be38d24e5bdd50190304"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:39Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/snapshot.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/snapshot.json
new file mode 100644
index 0000000..32c914d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "98ca9f817a41745409386dd26a433907349df18d1153b660e8d3cfa01d7dabc35aa06947e9cfac1d93edd7d9276246432fdf647a3357c2185d250d9ddc2be90f"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/targets.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/targets.json
new file mode 100644
index 0000000..7b1f6e0
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "70a93be0db6e7180088029c94283f47e60bfae3001982303e4060d952fe8e2db92e861f66bfcf32a4eb10e5a00900eaa719858945a314259c29b14cf39e73f03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:39Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/timestamp.json b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/timestamp.json
new file mode 100644
index 0000000..a8cdfcf
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_forwardRootVersion/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "05e65cc7a4ebf2dce8472f64c2a69dcee977d5bf8dddc36f4547882782e0d1161a5a03102155b29c41cff10b2f31ff947f6c604d07da1a9ff7eb423477a39c08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:39Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "1ce608f9525f5581f719ed696f6d445ec61ce9a0f97b0c3250bcebb687eb70fe",
+ "sha512": "22c79c8cb721c21e28cf4ae4e0c31bece6d4f55ed9234f4b538dbbff0c99eb733b2d6e73eaf66e3d9ed955c57a054470d8bfa740487aff0c470a463b7bd967f3"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/hash.txt b/client/testdata/Published3Times_keyrotated_initialrootsexpired/hash.txt
new file mode 100644
index 0000000..4011d3e
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/hash.txt
@@ -0,0 +1 @@
+7dcc997b6faa38805ccb1c5ce6024f59712b26461b4954dfb3c241df735db994 \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/2.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..00ab76d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "8f6e2a5a714d0157ab60191cdd2cb6dcb101f00c95b985bfaafd85637e94978a6493ea89535c8e37c4ac5f236c4329ba63e7eac721def60f03d799ae9cbe8204"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "2e6b955314910c3f3b91e9d7b6673afb6b6dcaef28b2a86172f4032228a96585b5f1cbb0182f64555329d724bde28980e9f72dfae019f777b256e9f9dabb620a"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/3.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/3.root.json
new file mode 100644
index 0000000..1fe6df3
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/3.root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "72c33cb0f06b935b61441ab63da4d2e4b207c5051f70b962c546efb7ebcb60890e6a96a95f02f368524687296b09d34168f4b9b5ee2b49a2b693476cbafa530f"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "8814eeb64bd2e7b88b6f4847085bb1b768d2ed33c0c53292d2a490074a6293be13fd5a03f35c685a17eccfe6f209e6cebf884cffcf706e9395793e23d56cdc0c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "cfae718a6ddda42bfb231fc7d6f5db98bbf80303c31a8ae38c303c61e001895cc6e433afe42c7343fb82eed7060f01dc9634829b4af39562b2c389b4d2c9c903"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/root.json
new file mode 100644
index 0000000..1fe6df3
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "72c33cb0f06b935b61441ab63da4d2e4b207c5051f70b962c546efb7ebcb60890e6a96a95f02f368524687296b09d34168f4b9b5ee2b49a2b693476cbafa530f"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "8814eeb64bd2e7b88b6f4847085bb1b768d2ed33c0c53292d2a490074a6293be13fd5a03f35c685a17eccfe6f209e6cebf884cffcf706e9395793e23d56cdc0c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "cfae718a6ddda42bfb231fc7d6f5db98bbf80303c31a8ae38c303c61e001895cc6e433afe42c7343fb82eed7060f01dc9634829b4af39562b2c389b4d2c9c903"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.root.json
new file mode 100644
index 0000000..b0cfa49
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c981b331d9fd0adec189ec55593735f05ded1d73d7999d67b882f56d2d2a201525d39c20a99cc9c1f03e9425ec607b1f57c7e24b540c0b0c671992cab26e4d07"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/2.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/2.root.json
new file mode 100644
index 0000000..00ab76d
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "8f6e2a5a714d0157ab60191cdd2cb6dcb101f00c95b985bfaafd85637e94978a6493ea89535c8e37c4ac5f236c4329ba63e7eac721def60f03d799ae9cbe8204"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "2e6b955314910c3f3b91e9d7b6673afb6b6dcaef28b2a86172f4032228a96585b5f1cbb0182f64555329d724bde28980e9f72dfae019f777b256e9f9dabb620a"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:47Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/3.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/3.root.json
new file mode 100644
index 0000000..1fe6df3
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/3.root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "72c33cb0f06b935b61441ab63da4d2e4b207c5051f70b962c546efb7ebcb60890e6a96a95f02f368524687296b09d34168f4b9b5ee2b49a2b693476cbafa530f"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "8814eeb64bd2e7b88b6f4847085bb1b768d2ed33c0c53292d2a490074a6293be13fd5a03f35c685a17eccfe6f209e6cebf884cffcf706e9395793e23d56cdc0c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "cfae718a6ddda42bfb231fc7d6f5db98bbf80303c31a8ae38c303c61e001895cc6e433afe42c7343fb82eed7060f01dc9634829b4af39562b2c389b4d2c9c903"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/root.json
new file mode 100644
index 0000000..1fe6df3
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "72c33cb0f06b935b61441ab63da4d2e4b207c5051f70b962c546efb7ebcb60890e6a96a95f02f368524687296b09d34168f4b9b5ee2b49a2b693476cbafa530f"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "8814eeb64bd2e7b88b6f4847085bb1b768d2ed33c0c53292d2a490074a6293be13fd5a03f35c685a17eccfe6f209e6cebf884cffcf706e9395793e23d56cdc0c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "cfae718a6ddda42bfb231fc7d6f5db98bbf80303c31a8ae38c303c61e001895cc6e433afe42c7343fb82eed7060f01dc9634829b4af39562b2c389b4d2c9c903"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:37Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/snapshot.json
new file mode 100644
index 0000000..42d8aae
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "ecf4d79a88873efb223361e550b8fc35813ad7bf31d9769908d9495cfb930e03db889dc7419897031cadf39437c9f49b085f384fe1c18be3961704062365db0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/targets.json
new file mode 100644
index 0000000..65bfd77
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "f2cee9e31bda3f88b25b0153bb48a6cb21237e4c41a34bbae95f152b56975085bc04c4978eed6be76a3ead23ff76a1259ff07e7b45b04fb51865c9601713c905"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:37Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/timestamp.json
new file mode 100644
index 0000000..9a03f46
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "35d1087c4874221bc772c8276bd77415d9366833aad4efc3926a8eda1de053843d2c114ac7448f08e3cfd802857c2ab2569d8be186b3cc36e65e458905f0e10e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:37Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "d62f650c8097fd4de9e3c8874ef15f7e10fdcc4c32f7a1818e972e8984b8da8a",
+ "sha512": "d9d0c38df27bc2ccc9523cb8d27623cb40b264053233e5e3261d83e2921c790b21accf9d427e36aa4c1939ddbccaaa5e006267f1cdd451606c514135d178dca5"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/2.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/2.root.json
new file mode 100644
index 0000000..6646aca
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0e22c864c9537c692cf66467310173896c3416d9b48a8c33f33c6f6b0117cd77c194f5d4695fe0f81efd77d3ebfe70d33421f4859374c133de8a247b51625906"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "a40c02d3ea080e4a46a09796926040127a7d8ed5f91ac2acee3a995b8eca403cc787b33f56840983f6975e886d3d5ceb157e1fa999b4ed699d70031a470fc508"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/root.json
new file mode 100644
index 0000000..6646aca
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0e22c864c9537c692cf66467310173896c3416d9b48a8c33f33c6f6b0117cd77c194f5d4695fe0f81efd77d3ebfe70d33421f4859374c133de8a247b51625906"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "a40c02d3ea080e4a46a09796926040127a7d8ed5f91ac2acee3a995b8eca403cc787b33f56840983f6975e886d3d5ceb157e1fa999b4ed699d70031a470fc508"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/2.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/2.root.json
new file mode 100644
index 0000000..6646aca
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0e22c864c9537c692cf66467310173896c3416d9b48a8c33f33c6f6b0117cd77c194f5d4695fe0f81efd77d3ebfe70d33421f4859374c133de8a247b51625906"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "a40c02d3ea080e4a46a09796926040127a7d8ed5f91ac2acee3a995b8eca403cc787b33f56840983f6975e886d3d5ceb157e1fa999b4ed699d70031a470fc508"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/root.json
new file mode 100644
index 0000000..6646aca
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0e22c864c9537c692cf66467310173896c3416d9b48a8c33f33c6f6b0117cd77c194f5d4695fe0f81efd77d3ebfe70d33421f4859374c133de8a247b51625906"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "a40c02d3ea080e4a46a09796926040127a7d8ed5f91ac2acee3a995b8eca403cc787b33f56840983f6975e886d3d5ceb157e1fa999b4ed699d70031a470fc508"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/hash.txt b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/hash.txt
new file mode 100644
index 0000000..2f45092
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/hash.txt
@@ -0,0 +1 @@
+f8e3fe50a810a933f4895e479fc7352d60762276a99b6554073a0f68316dbd6a \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/2.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..6646aca
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0e22c864c9537c692cf66467310173896c3416d9b48a8c33f33c6f6b0117cd77c194f5d4695fe0f81efd77d3ebfe70d33421f4859374c133de8a247b51625906"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "a40c02d3ea080e4a46a09796926040127a7d8ed5f91ac2acee3a995b8eca403cc787b33f56840983f6975e886d3d5ceb157e1fa999b4ed699d70031a470fc508"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/3.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/3.root.json
new file mode 100644
index 0000000..6a3a50e
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/3.root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "3c7663e43e6eedc42d62a2c2ea9ccc76186f105f6adab425245ea0610fdedd030685f7b707b4d88df2d80a942e13276da8330ae4160dabcedc46e1408d1dd803"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9d8e34d3a8c6cf665657d07053d0c77d8d4da801e3049af04b60a98b252523ad691e84c56af4efcadd83da981939d85cb22dbb7304fb5f88be044fbb8ecad408"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "138b6d748491e80c1108a2916dcb3033a0029db01d82539972cb10c5ba22366391ece7d97483494815d758ae08514eab3edd58b1f84ff603b67c28855d5bee0e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/root.json
new file mode 100644
index 0000000..6a3a50e
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "3c7663e43e6eedc42d62a2c2ea9ccc76186f105f6adab425245ea0610fdedd030685f7b707b4d88df2d80a942e13276da8330ae4160dabcedc46e1408d1dd803"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9d8e34d3a8c6cf665657d07053d0c77d8d4da801e3049af04b60a98b252523ad691e84c56af4efcadd83da981939d85cb22dbb7304fb5f88be044fbb8ecad408"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "138b6d748491e80c1108a2916dcb3033a0029db01d82539972cb10c5ba22366391ece7d97483494815d758ae08514eab3edd58b1f84ff603b67c28855d5bee0e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/2.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/2.root.json
new file mode 100644
index 0000000..6646aca
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0e22c864c9537c692cf66467310173896c3416d9b48a8c33f33c6f6b0117cd77c194f5d4695fe0f81efd77d3ebfe70d33421f4859374c133de8a247b51625906"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "a40c02d3ea080e4a46a09796926040127a7d8ed5f91ac2acee3a995b8eca403cc787b33f56840983f6975e886d3d5ceb157e1fa999b4ed699d70031a470fc508"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/3.root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/3.root.json
new file mode 100644
index 0000000..6a3a50e
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/3.root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "3c7663e43e6eedc42d62a2c2ea9ccc76186f105f6adab425245ea0610fdedd030685f7b707b4d88df2d80a942e13276da8330ae4160dabcedc46e1408d1dd803"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9d8e34d3a8c6cf665657d07053d0c77d8d4da801e3049af04b60a98b252523ad691e84c56af4efcadd83da981939d85cb22dbb7304fb5f88be044fbb8ecad408"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "138b6d748491e80c1108a2916dcb3033a0029db01d82539972cb10c5ba22366391ece7d97483494815d758ae08514eab3edd58b1f84ff603b67c28855d5bee0e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/root.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/root.json
new file mode 100644
index 0000000..6a3a50e
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "3c7663e43e6eedc42d62a2c2ea9ccc76186f105f6adab425245ea0610fdedd030685f7b707b4d88df2d80a942e13276da8330ae4160dabcedc46e1408d1dd803"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9d8e34d3a8c6cf665657d07053d0c77d8d4da801e3049af04b60a98b252523ad691e84c56af4efcadd83da981939d85cb22dbb7304fb5f88be044fbb8ecad408"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "138b6d748491e80c1108a2916dcb3033a0029db01d82539972cb10c5ba22366391ece7d97483494815d758ae08514eab3edd58b1f84ff603b67c28855d5bee0e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:13:38Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/snapshot.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/targets.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/timestamp.json b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_initialrootsexpired_clientversionis2/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.snapshot.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.targets.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.timestamp.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/snapshot.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/targets.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/timestamp.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.snapshot.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.targets.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.timestamp.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/snapshot.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/targets.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/timestamp.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/hash.txt b/client/testdata/Published3Times_keyrotated_latestrootexpired/hash.txt
new file mode 100644
index 0000000..3e15a17
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/hash.txt
@@ -0,0 +1 @@
+53afeac61b95af3060968d5bf4acdc71d4eded57ded489974ce47223708532ff \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.snapshot.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.targets.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.timestamp.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/2.root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..6646aca
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0e22c864c9537c692cf66467310173896c3416d9b48a8c33f33c6f6b0117cd77c194f5d4695fe0f81efd77d3ebfe70d33421f4859374c133de8a247b51625906"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "a40c02d3ea080e4a46a09796926040127a7d8ed5f91ac2acee3a995b8eca403cc787b33f56840983f6975e886d3d5ceb157e1fa999b4ed699d70031a470fc508"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/3.root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/3.root.json
new file mode 100644
index 0000000..a5ad77b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/3.root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5f39777693b8c95b3f1a76172cc147ca6c20acec65c4cd932fda605d6457b10d90e37985197bd6ace2266ea336c16d1669cf2b4b3c1d8502d1b2b1f30fe82c02"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "07dde55a3e8182d37b05b3edcf8d559fd088100827d5a697cc3dcafb875da2afa657cc635cbbe2c464c936c336408f31063b0dd9821b56e64d83eedbe7397f01"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e62976570275736532867c395345568051d3f01d5f8c723afb1d6d44b7e901f3b98a5ae4e21406914f1ca46cee40c9ea5d7854d574694d8dc63da499b2746006"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/root.json
new file mode 100644
index 0000000..a5ad77b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5f39777693b8c95b3f1a76172cc147ca6c20acec65c4cd932fda605d6457b10d90e37985197bd6ace2266ea336c16d1669cf2b4b3c1d8502d1b2b1f30fe82c02"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "07dde55a3e8182d37b05b3edcf8d559fd088100827d5a697cc3dcafb875da2afa657cc635cbbe2c464c936c336408f31063b0dd9821b56e64d83eedbe7397f01"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e62976570275736532867c395345568051d3f01d5f8c723afb1d6d44b7e901f3b98a5ae4e21406914f1ca46cee40c9ea5d7854d574694d8dc63da499b2746006"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/snapshot.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/targets.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/timestamp.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.root.json
new file mode 100644
index 0000000..184232b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "1a1565cf213002416129b7ff558fbe0b4b296984c756e5f67fe10e8b6dae5ccd372b17c9524fd2a92fe79df50c2845baa57a3447e2422bf5dc1c35e00e5e1003"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.snapshot.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.targets.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.timestamp.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/2.root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/2.root.json
new file mode 100644
index 0000000..6646aca
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/2.root.json
@@ -0,0 +1,103 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0e22c864c9537c692cf66467310173896c3416d9b48a8c33f33c6f6b0117cd77c194f5d4695fe0f81efd77d3ebfe70d33421f4859374c133de8a247b51625906"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "a40c02d3ea080e4a46a09796926040127a7d8ed5f91ac2acee3a995b8eca403cc787b33f56840983f6975e886d3d5ceb157e1fa999b4ed699d70031a470fc508"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/3.root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/3.root.json
new file mode 100644
index 0000000..a5ad77b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/3.root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5f39777693b8c95b3f1a76172cc147ca6c20acec65c4cd932fda605d6457b10d90e37985197bd6ace2266ea336c16d1669cf2b4b3c1d8502d1b2b1f30fe82c02"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "07dde55a3e8182d37b05b3edcf8d559fd088100827d5a697cc3dcafb875da2afa657cc635cbbe2c464c936c336408f31063b0dd9821b56e64d83eedbe7397f01"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e62976570275736532867c395345568051d3f01d5f8c723afb1d6d44b7e901f3b98a5ae4e21406914f1ca46cee40c9ea5d7854d574694d8dc63da499b2746006"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/root.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/root.json
new file mode 100644
index 0000000..a5ad77b
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/root.json
@@ -0,0 +1,119 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5f39777693b8c95b3f1a76172cc147ca6c20acec65c4cd932fda605d6457b10d90e37985197bd6ace2266ea336c16d1669cf2b4b3c1d8502d1b2b1f30fe82c02"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "07dde55a3e8182d37b05b3edcf8d559fd088100827d5a697cc3dcafb875da2afa657cc635cbbe2c464c936c336408f31063b0dd9821b56e64d83eedbe7397f01"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e62976570275736532867c395345568051d3f01d5f8c723afb1d6d44b7e901f3b98a5ae4e21406914f1ca46cee40c9ea5d7854d574694d8dc63da499b2746006"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2021-09-12T03:13:48Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/snapshot.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/snapshot.json
new file mode 100644
index 0000000..cff50ed
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "03f7436d986edb991e9812b64fa7a93229e2e73ab419e29feab828a311a5aace9b385ed48d44f602af25b8e3070eaf948c89bc59ab75fb8a1231882df1ef4a05"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/targets.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/targets.json
new file mode 100644
index 0000000..80777dc
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5d60d0018367fda4bbaa0ebb3b0ee0c2cdbac3bdb233369590f3055ad9d4299fde6a8a8824ed3f38129ca6ee3fa67075b6591169aa1819a6238ea1821dbaa03"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:13:38Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/timestamp.json b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/timestamp.json
new file mode 100644
index 0000000..221e780
--- /dev/null
+++ b/client/testdata/Published3Times_keyrotated_latestrootexpired/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c5a41119ccf10925e7c065c10c14d3690b6c4071fa4f2f7e3bc5944e375ee28ccf81721c9f506d788e0cc9b3d335791a4553f3f1e39873903f00a449a3de5004"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:13:38Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "700c180d3a3d6e2fa86f4bb072d7f81f39f1cc1dc0df32bb6b5e89a715f743d2",
+ "sha512": "5b21d7bda6e29381e7f095204eddd78137cc14416585c3e9f8915ce21ff1b17b646932b37d7a1147cc0f1d6b7f6ea75d91f6b63d7e229fe73c0b4f1845c40f41"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/hash.txt b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/hash.txt
new file mode 100644
index 0000000..8bedf9d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/hash.txt
@@ -0,0 +1 @@
+1b82d8de8e29a4cff4fa146b19aefdc45d3016b8b580c84d1252d0b38d008343 \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..abb8737
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/2.root.json
@@ -0,0 +1,191 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "496695b909566a8c2c4649e334c1776b010116e7d1860d855bd53f0f0466c41f733b914828bd4134cdf3d1b2bff1961482f3aaf1dcb848c8dbe63c2c0c4cea0a"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "52a32e1b2f680c803c2c79b2083be98abf05a8528a5155cff728578b21fce4b7b75a186875ff8e5b387feb6a6badc2b4bf86497a1e82830cc62636ffcc10850f"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1543bcd29a9ba609f50ce9cce682ceadba8df939b84dd06851f71bcea767f305580c69253179608b3fe8d3fe179158e4ee272f0e04bcc7a9aabf845dfccbcb02"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9a6c51a85bc11289755a7f4f61e6a8b21749d70bc1ee26b8ea77ddad49871e052aef72066ebd390632db4f4a88d093ec9c96bb073d63d535f7dbd4122be05a0b"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "361ee22a20fe33cec10fc2fd807c542d3e981739b83fbb5346d94268cfa5bb5c47bfff94a8a48de0011e7dc789eaca880df3474deb8e5a6fb0147fe19d99570e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "c1ad1c1192587704f6aa3dd8f26b1fef81145676014ca3507d734eb1b1c5f07541573ae1e0641bb9a1d176f6459d34e8668fe3227f6eb85deb325309bce8d80f"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c7ec24a2dd742793056036bb2ce233b9750d633af21dcba4e7a33a9bb1879dc2664670ae7d15f482bfba54ac2a1844bc14a0859e22331657ac948880f543a70d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "4f693edf9fef84b4fded4107d5958a7578bf589422920d016155d189f09d415a0511055f723746840a1402eb80c7b693dd2dc8496a250308b8a4477172ec1104"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "d88a622ae383cc75dd2f96629dd408483285afd450bdad37f1ade6a105a2367ea35200e8f74e9f29a4843308d59d1d29dbc12536e12a5eb00c4570a04606f300"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/root.json
new file mode 100644
index 0000000..abb8737
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/root.json
@@ -0,0 +1,191 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "496695b909566a8c2c4649e334c1776b010116e7d1860d855bd53f0f0466c41f733b914828bd4134cdf3d1b2bff1961482f3aaf1dcb848c8dbe63c2c0c4cea0a"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "52a32e1b2f680c803c2c79b2083be98abf05a8528a5155cff728578b21fce4b7b75a186875ff8e5b387feb6a6badc2b4bf86497a1e82830cc62636ffcc10850f"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1543bcd29a9ba609f50ce9cce682ceadba8df939b84dd06851f71bcea767f305580c69253179608b3fe8d3fe179158e4ee272f0e04bcc7a9aabf845dfccbcb02"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9a6c51a85bc11289755a7f4f61e6a8b21749d70bc1ee26b8ea77ddad49871e052aef72066ebd390632db4f4a88d093ec9c96bb073d63d535f7dbd4122be05a0b"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "361ee22a20fe33cec10fc2fd807c542d3e981739b83fbb5346d94268cfa5bb5c47bfff94a8a48de0011e7dc789eaca880df3474deb8e5a6fb0147fe19d99570e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "c1ad1c1192587704f6aa3dd8f26b1fef81145676014ca3507d734eb1b1c5f07541573ae1e0641bb9a1d176f6459d34e8668fe3227f6eb85deb325309bce8d80f"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c7ec24a2dd742793056036bb2ce233b9750d633af21dcba4e7a33a9bb1879dc2664670ae7d15f482bfba54ac2a1844bc14a0859e22331657ac948880f543a70d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "4f693edf9fef84b4fded4107d5958a7578bf589422920d016155d189f09d415a0511055f723746840a1402eb80c7b693dd2dc8496a250308b8a4477172ec1104"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "d88a622ae383cc75dd2f96629dd408483285afd450bdad37f1ade6a105a2367ea35200e8f74e9f29a4843308d59d1d29dbc12536e12a5eb00c4570a04606f300"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/2.root.json
new file mode 100644
index 0000000..abb8737
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/2.root.json
@@ -0,0 +1,191 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "496695b909566a8c2c4649e334c1776b010116e7d1860d855bd53f0f0466c41f733b914828bd4134cdf3d1b2bff1961482f3aaf1dcb848c8dbe63c2c0c4cea0a"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "52a32e1b2f680c803c2c79b2083be98abf05a8528a5155cff728578b21fce4b7b75a186875ff8e5b387feb6a6badc2b4bf86497a1e82830cc62636ffcc10850f"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1543bcd29a9ba609f50ce9cce682ceadba8df939b84dd06851f71bcea767f305580c69253179608b3fe8d3fe179158e4ee272f0e04bcc7a9aabf845dfccbcb02"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9a6c51a85bc11289755a7f4f61e6a8b21749d70bc1ee26b8ea77ddad49871e052aef72066ebd390632db4f4a88d093ec9c96bb073d63d535f7dbd4122be05a0b"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "361ee22a20fe33cec10fc2fd807c542d3e981739b83fbb5346d94268cfa5bb5c47bfff94a8a48de0011e7dc789eaca880df3474deb8e5a6fb0147fe19d99570e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "c1ad1c1192587704f6aa3dd8f26b1fef81145676014ca3507d734eb1b1c5f07541573ae1e0641bb9a1d176f6459d34e8668fe3227f6eb85deb325309bce8d80f"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c7ec24a2dd742793056036bb2ce233b9750d633af21dcba4e7a33a9bb1879dc2664670ae7d15f482bfba54ac2a1844bc14a0859e22331657ac948880f543a70d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "4f693edf9fef84b4fded4107d5958a7578bf589422920d016155d189f09d415a0511055f723746840a1402eb80c7b693dd2dc8496a250308b8a4477172ec1104"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "d88a622ae383cc75dd2f96629dd408483285afd450bdad37f1ade6a105a2367ea35200e8f74e9f29a4843308d59d1d29dbc12536e12a5eb00c4570a04606f300"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/root.json
new file mode 100644
index 0000000..abb8737
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/root.json
@@ -0,0 +1,191 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "496695b909566a8c2c4649e334c1776b010116e7d1860d855bd53f0f0466c41f733b914828bd4134cdf3d1b2bff1961482f3aaf1dcb848c8dbe63c2c0c4cea0a"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "52a32e1b2f680c803c2c79b2083be98abf05a8528a5155cff728578b21fce4b7b75a186875ff8e5b387feb6a6badc2b4bf86497a1e82830cc62636ffcc10850f"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1543bcd29a9ba609f50ce9cce682ceadba8df939b84dd06851f71bcea767f305580c69253179608b3fe8d3fe179158e4ee272f0e04bcc7a9aabf845dfccbcb02"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9a6c51a85bc11289755a7f4f61e6a8b21749d70bc1ee26b8ea77ddad49871e052aef72066ebd390632db4f4a88d093ec9c96bb073d63d535f7dbd4122be05a0b"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "361ee22a20fe33cec10fc2fd807c542d3e981739b83fbb5346d94268cfa5bb5c47bfff94a8a48de0011e7dc789eaca880df3474deb8e5a6fb0147fe19d99570e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "c1ad1c1192587704f6aa3dd8f26b1fef81145676014ca3507d734eb1b1c5f07541573ae1e0641bb9a1d176f6459d34e8668fe3227f6eb85deb325309bce8d80f"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "c7ec24a2dd742793056036bb2ce233b9750d633af21dcba4e7a33a9bb1879dc2664670ae7d15f482bfba54ac2a1844bc14a0859e22331657ac948880f543a70d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "4f693edf9fef84b4fded4107d5958a7578bf589422920d016155d189f09d415a0511055f723746840a1402eb80c7b693dd2dc8496a250308b8a4477172ec1104"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "d88a622ae383cc75dd2f96629dd408483285afd450bdad37f1ade6a105a2367ea35200e8f74e9f29a4843308d59d1d29dbc12536e12a5eb00c4570a04606f300"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_root/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/hash.txt b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/hash.txt
new file mode 100644
index 0000000..36a468d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/hash.txt
@@ -0,0 +1 @@
+2818c21e8ecee671a52421bedf8cbe7df0e4b7ed45cdf9fa17a10f26bd90989e \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..0c435fe
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/2.root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "06aa273601715be06bd54ca5f74d7e8e5e5130646a2a7e21fbcef3d30167bf001805f3df25b06b8c0de5e1d17dba8562c454011262c6523a4e64ce24390b4d0c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/2.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/2.snapshot.json
new file mode 100644
index 0000000..7cb7305
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/2.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "25cd6eee4f580fed660330de02cb9ab34b9e7495d3f4c57babf225c3c2c39a0937afe633a7760c19681c6b05d89f2c3907a7eef32822992624223cfbcb872d00"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2011938c47cf0990172acc5a418b9758d124655200b6874fb6646bd6025de6c5a48a3c30ced83b1f955b242c1c3f4d2d346b71861fccdc45c945418aa2511d0e"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "303b7b1062a09644bd18804b6b1af6818c25abd212b6539cab0fab0e729a4eb14621396fde4d809649c37b82db5278e878b21b52052ac4fecbcff471c8f64408"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "85961c73d21ab17e5892681198479700a995920cd02001818979fe35122e5776fcc3fd499811b1f995f922ad737d61c5b72229ab1e4d7aab63d9ea1642795c0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "4aed1e9ef8d1ac785d15df386470df92ff484f71c35a37ecd1e83e0fd66e67206172f6a2f277bdebae17372e75d253b45143adf3f9d8b6d23a4690d482e2180b"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "26ed17502d130ab4c7b26a1ef78730d9f15847cc3aa6623f692003e588554761b6f6c3b60da304933b3e012478466ce634970562504fdab9b6d2c8e818f3220e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "0b498cf7cd3a926007f158d9b72002283d67f4f0c2e4a61d5bb3d3afe09d0e9dcd41a4bc67d5cc87a2d072028904d2e022ca4d2abc84137433cdd6812f613505"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1f9aea75e9b632f06769ac0aa611ce3116faa14a3736cddc4cdbcae5f7084a0cd44c4a44a8c78a4200f34b097c0176c8c686b352c79105ffcfd764473f9d3206"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3f315ca76675299894f1ad111dcce7450e1e2f06400e64a86db5d42c67a14fc7a8f92b1063ae547229544d046bcbb038027e0e0abab05bfb6600f7349a9bf90a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/root.json
new file mode 100644
index 0000000..0c435fe
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "06aa273601715be06bd54ca5f74d7e8e5e5130646a2a7e21fbcef3d30167bf001805f3df25b06b8c0de5e1d17dba8562c454011262c6523a4e64ce24390b4d0c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..7cb7305
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "25cd6eee4f580fed660330de02cb9ab34b9e7495d3f4c57babf225c3c2c39a0937afe633a7760c19681c6b05d89f2c3907a7eef32822992624223cfbcb872d00"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2011938c47cf0990172acc5a418b9758d124655200b6874fb6646bd6025de6c5a48a3c30ced83b1f955b242c1c3f4d2d346b71861fccdc45c945418aa2511d0e"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "303b7b1062a09644bd18804b6b1af6818c25abd212b6539cab0fab0e729a4eb14621396fde4d809649c37b82db5278e878b21b52052ac4fecbcff471c8f64408"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "85961c73d21ab17e5892681198479700a995920cd02001818979fe35122e5776fcc3fd499811b1f995f922ad737d61c5b72229ab1e4d7aab63d9ea1642795c0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "4aed1e9ef8d1ac785d15df386470df92ff484f71c35a37ecd1e83e0fd66e67206172f6a2f277bdebae17372e75d253b45143adf3f9d8b6d23a4690d482e2180b"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "26ed17502d130ab4c7b26a1ef78730d9f15847cc3aa6623f692003e588554761b6f6c3b60da304933b3e012478466ce634970562504fdab9b6d2c8e818f3220e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "0b498cf7cd3a926007f158d9b72002283d67f4f0c2e4a61d5bb3d3afe09d0e9dcd41a4bc67d5cc87a2d072028904d2e022ca4d2abc84137433cdd6812f613505"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1f9aea75e9b632f06769ac0aa611ce3116faa14a3736cddc4cdbcae5f7084a0cd44c4a44a8c78a4200f34b097c0176c8c686b352c79105ffcfd764473f9d3206"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3f315ca76675299894f1ad111dcce7450e1e2f06400e64a86db5d42c67a14fc7a8f92b1063ae547229544d046bcbb038027e0e0abab05bfb6600f7349a9bf90a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/2.root.json
new file mode 100644
index 0000000..0c435fe
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/2.root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "06aa273601715be06bd54ca5f74d7e8e5e5130646a2a7e21fbcef3d30167bf001805f3df25b06b8c0de5e1d17dba8562c454011262c6523a4e64ce24390b4d0c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/2.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/2.snapshot.json
new file mode 100644
index 0000000..7cb7305
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/2.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "25cd6eee4f580fed660330de02cb9ab34b9e7495d3f4c57babf225c3c2c39a0937afe633a7760c19681c6b05d89f2c3907a7eef32822992624223cfbcb872d00"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2011938c47cf0990172acc5a418b9758d124655200b6874fb6646bd6025de6c5a48a3c30ced83b1f955b242c1c3f4d2d346b71861fccdc45c945418aa2511d0e"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "303b7b1062a09644bd18804b6b1af6818c25abd212b6539cab0fab0e729a4eb14621396fde4d809649c37b82db5278e878b21b52052ac4fecbcff471c8f64408"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "85961c73d21ab17e5892681198479700a995920cd02001818979fe35122e5776fcc3fd499811b1f995f922ad737d61c5b72229ab1e4d7aab63d9ea1642795c0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "4aed1e9ef8d1ac785d15df386470df92ff484f71c35a37ecd1e83e0fd66e67206172f6a2f277bdebae17372e75d253b45143adf3f9d8b6d23a4690d482e2180b"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "26ed17502d130ab4c7b26a1ef78730d9f15847cc3aa6623f692003e588554761b6f6c3b60da304933b3e012478466ce634970562504fdab9b6d2c8e818f3220e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "0b498cf7cd3a926007f158d9b72002283d67f4f0c2e4a61d5bb3d3afe09d0e9dcd41a4bc67d5cc87a2d072028904d2e022ca4d2abc84137433cdd6812f613505"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1f9aea75e9b632f06769ac0aa611ce3116faa14a3736cddc4cdbcae5f7084a0cd44c4a44a8c78a4200f34b097c0176c8c686b352c79105ffcfd764473f9d3206"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3f315ca76675299894f1ad111dcce7450e1e2f06400e64a86db5d42c67a14fc7a8f92b1063ae547229544d046bcbb038027e0e0abab05bfb6600f7349a9bf90a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/root.json
new file mode 100644
index 0000000..0c435fe
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "06aa273601715be06bd54ca5f74d7e8e5e5130646a2a7e21fbcef3d30167bf001805f3df25b06b8c0de5e1d17dba8562c454011262c6523a4e64ce24390b4d0c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/snapshot.json
new file mode 100644
index 0000000..7cb7305
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "25cd6eee4f580fed660330de02cb9ab34b9e7495d3f4c57babf225c3c2c39a0937afe633a7760c19681c6b05d89f2c3907a7eef32822992624223cfbcb872d00"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2011938c47cf0990172acc5a418b9758d124655200b6874fb6646bd6025de6c5a48a3c30ced83b1f955b242c1c3f4d2d346b71861fccdc45c945418aa2511d0e"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "303b7b1062a09644bd18804b6b1af6818c25abd212b6539cab0fab0e729a4eb14621396fde4d809649c37b82db5278e878b21b52052ac4fecbcff471c8f64408"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "85961c73d21ab17e5892681198479700a995920cd02001818979fe35122e5776fcc3fd499811b1f995f922ad737d61c5b72229ab1e4d7aab63d9ea1642795c0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "4aed1e9ef8d1ac785d15df386470df92ff484f71c35a37ecd1e83e0fd66e67206172f6a2f277bdebae17372e75d253b45143adf3f9d8b6d23a4690d482e2180b"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "26ed17502d130ab4c7b26a1ef78730d9f15847cc3aa6623f692003e588554761b6f6c3b60da304933b3e012478466ce634970562504fdab9b6d2c8e818f3220e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "0b498cf7cd3a926007f158d9b72002283d67f4f0c2e4a61d5bb3d3afe09d0e9dcd41a4bc67d5cc87a2d072028904d2e022ca4d2abc84137433cdd6812f613505"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1f9aea75e9b632f06769ac0aa611ce3116faa14a3736cddc4cdbcae5f7084a0cd44c4a44a8c78a4200f34b097c0176c8c686b352c79105ffcfd764473f9d3206"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3f315ca76675299894f1ad111dcce7450e1e2f06400e64a86db5d42c67a14fc7a8f92b1063ae547229544d046bcbb038027e0e0abab05bfb6600f7349a9bf90a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_snapshot/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.root.json
new file mode 100644
index 0000000..cdb0cfc
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e007d7f8d4013496a546585fa7a1bc6c793391bd119f84f213b70f70d177a2a48186b4f4892879d65e412c37256406eb0b7bc0467e29e6ae5e86da3addda6407"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..d96641d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "cbf785791cb07f08524bf07f064cd3dd2aa59d4df683817511186efcf66dc477e775f55669685b0ede916ab8715efab88b6fefb62da052eba03faded53406e0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "8940c7a6d7892be68c9830d339c709b5105d2fcccc0624e43c4f5444e25f7ec578ec4899398692edf0e7b91e1194b8cd2ab7c2fb120e1681cbfeca9066b50d01"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "3d4f10ef870798ec71187ef703fc134a148160a66db0478cef893b0a91487cb90e1e59e7b6b8880def22cd928472421c8ab6ada95d515080b5f483aa17e63b06"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "09c192fb46e99f6aa5b4e12a7a5c6239c509cc30926e2cceefdf09166621e1c7e945a40e0c12c19f62a5cd78e4627673c5b1da0f55af54f4bc8bec43728b6d0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3bbf88e8cac7256a72f7b681dec4b26cd83cc7c72ad048235a5bf16bbe9519a3c9990f404f61f3f8833d7235633cb821062738c863097aa3a0e25e36f371ef0f"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf6e9cd2defa1ae0020e27a91d36910a9a665e127d09f77b698a59d47e70913aa6ebf958057bdc68c2ce8e9d344fffd7e892af66fda424079c732900f3c5590a"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1089b0a143419b02067af4ad353ea03c8adce43370485ff257ff8b6e7006fcddfce8db023844aea960f09520a07f86500bf3be11c3cc71479b4fc2953a0a3d0f"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "e3673a85e84a24c12fb654e114d3702e5fa98ac73f11bcd72aa5304afa1cc8a30d53c0cacbd1d65bcb7ae7a2d34c7e46b58f8cb784765f15b1b4e6730aa1cb00"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..d32a863
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/root.json
new file mode 100644
index 0000000..cdb0cfc
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e007d7f8d4013496a546585fa7a1bc6c793391bd119f84f213b70f70d177a2a48186b4f4892879d65e412c37256406eb0b7bc0467e29e6ae5e86da3addda6407"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/targets.json
new file mode 100644
index 0000000..d96641d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "cbf785791cb07f08524bf07f064cd3dd2aa59d4df683817511186efcf66dc477e775f55669685b0ede916ab8715efab88b6fefb62da052eba03faded53406e0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "8940c7a6d7892be68c9830d339c709b5105d2fcccc0624e43c4f5444e25f7ec578ec4899398692edf0e7b91e1194b8cd2ab7c2fb120e1681cbfeca9066b50d01"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "3d4f10ef870798ec71187ef703fc134a148160a66db0478cef893b0a91487cb90e1e59e7b6b8880def22cd928472421c8ab6ada95d515080b5f483aa17e63b06"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "09c192fb46e99f6aa5b4e12a7a5c6239c509cc30926e2cceefdf09166621e1c7e945a40e0c12c19f62a5cd78e4627673c5b1da0f55af54f4bc8bec43728b6d0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3bbf88e8cac7256a72f7b681dec4b26cd83cc7c72ad048235a5bf16bbe9519a3c9990f404f61f3f8833d7235633cb821062738c863097aa3a0e25e36f371ef0f"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf6e9cd2defa1ae0020e27a91d36910a9a665e127d09f77b698a59d47e70913aa6ebf958057bdc68c2ce8e9d344fffd7e892af66fda424079c732900f3c5590a"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1089b0a143419b02067af4ad353ea03c8adce43370485ff257ff8b6e7006fcddfce8db023844aea960f09520a07f86500bf3be11c3cc71479b4fc2953a0a3d0f"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "e3673a85e84a24c12fb654e114d3702e5fa98ac73f11bcd72aa5304afa1cc8a30d53c0cacbd1d65bcb7ae7a2d34c7e46b58f8cb784765f15b1b4e6730aa1cb00"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..d32a863
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..cdb0cfc
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e007d7f8d4013496a546585fa7a1bc6c793391bd119f84f213b70f70d177a2a48186b4f4892879d65e412c37256406eb0b7bc0467e29e6ae5e86da3addda6407"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..d96641d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "cbf785791cb07f08524bf07f064cd3dd2aa59d4df683817511186efcf66dc477e775f55669685b0ede916ab8715efab88b6fefb62da052eba03faded53406e0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "8940c7a6d7892be68c9830d339c709b5105d2fcccc0624e43c4f5444e25f7ec578ec4899398692edf0e7b91e1194b8cd2ab7c2fb120e1681cbfeca9066b50d01"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "3d4f10ef870798ec71187ef703fc134a148160a66db0478cef893b0a91487cb90e1e59e7b6b8880def22cd928472421c8ab6ada95d515080b5f483aa17e63b06"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "09c192fb46e99f6aa5b4e12a7a5c6239c509cc30926e2cceefdf09166621e1c7e945a40e0c12c19f62a5cd78e4627673c5b1da0f55af54f4bc8bec43728b6d0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3bbf88e8cac7256a72f7b681dec4b26cd83cc7c72ad048235a5bf16bbe9519a3c9990f404f61f3f8833d7235633cb821062738c863097aa3a0e25e36f371ef0f"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf6e9cd2defa1ae0020e27a91d36910a9a665e127d09f77b698a59d47e70913aa6ebf958057bdc68c2ce8e9d344fffd7e892af66fda424079c732900f3c5590a"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1089b0a143419b02067af4ad353ea03c8adce43370485ff257ff8b6e7006fcddfce8db023844aea960f09520a07f86500bf3be11c3cc71479b4fc2953a0a3d0f"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "e3673a85e84a24c12fb654e114d3702e5fa98ac73f11bcd72aa5304afa1cc8a30d53c0cacbd1d65bcb7ae7a2d34c7e46b58f8cb784765f15b1b4e6730aa1cb00"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..d32a863
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/root.json
new file mode 100644
index 0000000..cdb0cfc
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e007d7f8d4013496a546585fa7a1bc6c793391bd119f84f213b70f70d177a2a48186b4f4892879d65e412c37256406eb0b7bc0467e29e6ae5e86da3addda6407"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/targets.json
new file mode 100644
index 0000000..d96641d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "cbf785791cb07f08524bf07f064cd3dd2aa59d4df683817511186efcf66dc477e775f55669685b0ede916ab8715efab88b6fefb62da052eba03faded53406e0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "8940c7a6d7892be68c9830d339c709b5105d2fcccc0624e43c4f5444e25f7ec578ec4899398692edf0e7b91e1194b8cd2ab7c2fb120e1681cbfeca9066b50d01"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "3d4f10ef870798ec71187ef703fc134a148160a66db0478cef893b0a91487cb90e1e59e7b6b8880def22cd928472421c8ab6ada95d515080b5f483aa17e63b06"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "09c192fb46e99f6aa5b4e12a7a5c6239c509cc30926e2cceefdf09166621e1c7e945a40e0c12c19f62a5cd78e4627673c5b1da0f55af54f4bc8bec43728b6d0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3bbf88e8cac7256a72f7b681dec4b26cd83cc7c72ad048235a5bf16bbe9519a3c9990f404f61f3f8833d7235633cb821062738c863097aa3a0e25e36f371ef0f"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf6e9cd2defa1ae0020e27a91d36910a9a665e127d09f77b698a59d47e70913aa6ebf958057bdc68c2ce8e9d344fffd7e892af66fda424079c732900f3c5590a"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1089b0a143419b02067af4ad353ea03c8adce43370485ff257ff8b6e7006fcddfce8db023844aea960f09520a07f86500bf3be11c3cc71479b4fc2953a0a3d0f"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "e3673a85e84a24c12fb654e114d3702e5fa98ac73f11bcd72aa5304afa1cc8a30d53c0cacbd1d65bcb7ae7a2d34c7e46b58f8cb784765f15b1b4e6730aa1cb00"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..d32a863
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/hash.txt b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/hash.txt
new file mode 100644
index 0000000..b4f24c8
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/hash.txt
@@ -0,0 +1 @@
+38d05f4a8fc9892deee2ab651bd61de98ed34393955851f1a9699d02d6c652a1 \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..cdb0cfc
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e007d7f8d4013496a546585fa7a1bc6c793391bd119f84f213b70f70d177a2a48186b4f4892879d65e412c37256406eb0b7bc0467e29e6ae5e86da3addda6407"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..d96641d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "cbf785791cb07f08524bf07f064cd3dd2aa59d4df683817511186efcf66dc477e775f55669685b0ede916ab8715efab88b6fefb62da052eba03faded53406e0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "8940c7a6d7892be68c9830d339c709b5105d2fcccc0624e43c4f5444e25f7ec578ec4899398692edf0e7b91e1194b8cd2ab7c2fb120e1681cbfeca9066b50d01"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "3d4f10ef870798ec71187ef703fc134a148160a66db0478cef893b0a91487cb90e1e59e7b6b8880def22cd928472421c8ab6ada95d515080b5f483aa17e63b06"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "09c192fb46e99f6aa5b4e12a7a5c6239c509cc30926e2cceefdf09166621e1c7e945a40e0c12c19f62a5cd78e4627673c5b1da0f55af54f4bc8bec43728b6d0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3bbf88e8cac7256a72f7b681dec4b26cd83cc7c72ad048235a5bf16bbe9519a3c9990f404f61f3f8833d7235633cb821062738c863097aa3a0e25e36f371ef0f"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf6e9cd2defa1ae0020e27a91d36910a9a665e127d09f77b698a59d47e70913aa6ebf958057bdc68c2ce8e9d344fffd7e892af66fda424079c732900f3c5590a"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1089b0a143419b02067af4ad353ea03c8adce43370485ff257ff8b6e7006fcddfce8db023844aea960f09520a07f86500bf3be11c3cc71479b4fc2953a0a3d0f"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "e3673a85e84a24c12fb654e114d3702e5fa98ac73f11bcd72aa5304afa1cc8a30d53c0cacbd1d65bcb7ae7a2d34c7e46b58f8cb784765f15b1b4e6730aa1cb00"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..d32a863
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..9a443e4
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/2.root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "dbfb546e3f361f7b746cc076d6632e5fdfe18ec0268b383de317af45f5cb704c9145707144f1f540e91b256b418cf5753de7391fdc1f8ff27c0a6eb265904005"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/2.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/2.targets.json
new file mode 100644
index 0000000..b9ec287
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/2.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "8c3d6300986d5859cdbc3d4a3509f4ca3e3e9dda0b9ccfd51ffcae65757f221a478ed0be7dbae9f1c6c1d10063e5eeec1f05b538c282e4f7de1d8970b9fe210c"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2a257763ea72c8d547083ff87438747c4d0f240a24dba479cdddc1bda562a20d929d770262c915d2c5ae79cd3854abebf263cb56668d1d0a347d4f0033dd360c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "163ec38d14a35b364e3c9f3c57a7d6aa7f5a178f8ec23a33ce43aab9eed34a9fbb813d8aebb5f91b278dc03061eb9528a457f1e16a449ff0ffc3c7e279be6204"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "bb4e58d3f96691280935c1f6e0b603739878ad6cacc2da6395d75c1e6fb9d2fb2e4de238634ec3bd417621485093e480490002c22114a321affb7b4994714c01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "c7e970795af37c2ec5e73a627daa2fd052d26764b47b70d84257b3607f2e9b4472da8eba5bd720dd4fc9801d9108a69d1bff78d8c0c6b50031bfe158ba1ca300"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "ddb6f6aa044d7b64831b3321d3aa3e3a0c3c1ef5144e9257204d0d7b8e6ac616c9f210166f7f679d8e120d8bdcbc40cfa1fc1a2faf97dbe90b5897a49bd6fe0f"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "7a5b9705304316df3edad16d934518b73a4ee29259e8d5accad5b9fb88c08ae0cd8a95b06f197d94b6ce09cfba528e63eec2464de56ecb5449bba4a2d110a705"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "a5fb7d23197cb5856ba1524676bf4f6bd96181b8f3a992607167f86a4053c0280651894d37667fbdd3ff21ed16a9f61116e969db4532935e79a7723f2c9af101"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "701c686cb7defb91040a58f94d9800f42a142d3efdcc53db84a2c1a89f372d134f631135ffeef676efa1cc8b90b4ebbb5040bfc513a7a2aa54cf087cf9770b06"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/root.json
new file mode 100644
index 0000000..9a443e4
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "dbfb546e3f361f7b746cc076d6632e5fdfe18ec0268b383de317af45f5cb704c9145707144f1f540e91b256b418cf5753de7391fdc1f8ff27c0a6eb265904005"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/targets.json
new file mode 100644
index 0000000..b9ec287
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "8c3d6300986d5859cdbc3d4a3509f4ca3e3e9dda0b9ccfd51ffcae65757f221a478ed0be7dbae9f1c6c1d10063e5eeec1f05b538c282e4f7de1d8970b9fe210c"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2a257763ea72c8d547083ff87438747c4d0f240a24dba479cdddc1bda562a20d929d770262c915d2c5ae79cd3854abebf263cb56668d1d0a347d4f0033dd360c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "163ec38d14a35b364e3c9f3c57a7d6aa7f5a178f8ec23a33ce43aab9eed34a9fbb813d8aebb5f91b278dc03061eb9528a457f1e16a449ff0ffc3c7e279be6204"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "bb4e58d3f96691280935c1f6e0b603739878ad6cacc2da6395d75c1e6fb9d2fb2e4de238634ec3bd417621485093e480490002c22114a321affb7b4994714c01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "c7e970795af37c2ec5e73a627daa2fd052d26764b47b70d84257b3607f2e9b4472da8eba5bd720dd4fc9801d9108a69d1bff78d8c0c6b50031bfe158ba1ca300"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "ddb6f6aa044d7b64831b3321d3aa3e3a0c3c1ef5144e9257204d0d7b8e6ac616c9f210166f7f679d8e120d8bdcbc40cfa1fc1a2faf97dbe90b5897a49bd6fe0f"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "7a5b9705304316df3edad16d934518b73a4ee29259e8d5accad5b9fb88c08ae0cd8a95b06f197d94b6ce09cfba528e63eec2464de56ecb5449bba4a2d110a705"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "a5fb7d23197cb5856ba1524676bf4f6bd96181b8f3a992607167f86a4053c0280651894d37667fbdd3ff21ed16a9f61116e969db4532935e79a7723f2c9af101"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "701c686cb7defb91040a58f94d9800f42a142d3efdcc53db84a2c1a89f372d134f631135ffeef676efa1cc8b90b4ebbb5040bfc513a7a2aa54cf087cf9770b06"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..d32a863
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.root.json
new file mode 100644
index 0000000..cdb0cfc
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e007d7f8d4013496a546585fa7a1bc6c793391bd119f84f213b70f70d177a2a48186b4f4892879d65e412c37256406eb0b7bc0467e29e6ae5e86da3addda6407"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.targets.json
new file mode 100644
index 0000000..d96641d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "cbf785791cb07f08524bf07f064cd3dd2aa59d4df683817511186efcf66dc477e775f55669685b0ede916ab8715efab88b6fefb62da052eba03faded53406e0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "8940c7a6d7892be68c9830d339c709b5105d2fcccc0624e43c4f5444e25f7ec578ec4899398692edf0e7b91e1194b8cd2ab7c2fb120e1681cbfeca9066b50d01"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "3d4f10ef870798ec71187ef703fc134a148160a66db0478cef893b0a91487cb90e1e59e7b6b8880def22cd928472421c8ab6ada95d515080b5f483aa17e63b06"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "09c192fb46e99f6aa5b4e12a7a5c6239c509cc30926e2cceefdf09166621e1c7e945a40e0c12c19f62a5cd78e4627673c5b1da0f55af54f4bc8bec43728b6d0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3bbf88e8cac7256a72f7b681dec4b26cd83cc7c72ad048235a5bf16bbe9519a3c9990f404f61f3f8833d7235633cb821062738c863097aa3a0e25e36f371ef0f"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf6e9cd2defa1ae0020e27a91d36910a9a665e127d09f77b698a59d47e70913aa6ebf958057bdc68c2ce8e9d344fffd7e892af66fda424079c732900f3c5590a"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1089b0a143419b02067af4ad353ea03c8adce43370485ff257ff8b6e7006fcddfce8db023844aea960f09520a07f86500bf3be11c3cc71479b4fc2953a0a3d0f"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "e3673a85e84a24c12fb654e114d3702e5fa98ac73f11bcd72aa5304afa1cc8a30d53c0cacbd1d65bcb7ae7a2d34c7e46b58f8cb784765f15b1b4e6730aa1cb00"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..d32a863
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/2.root.json
new file mode 100644
index 0000000..9a443e4
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/2.root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "dbfb546e3f361f7b746cc076d6632e5fdfe18ec0268b383de317af45f5cb704c9145707144f1f540e91b256b418cf5753de7391fdc1f8ff27c0a6eb265904005"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/2.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/2.targets.json
new file mode 100644
index 0000000..b9ec287
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/2.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "8c3d6300986d5859cdbc3d4a3509f4ca3e3e9dda0b9ccfd51ffcae65757f221a478ed0be7dbae9f1c6c1d10063e5eeec1f05b538c282e4f7de1d8970b9fe210c"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2a257763ea72c8d547083ff87438747c4d0f240a24dba479cdddc1bda562a20d929d770262c915d2c5ae79cd3854abebf263cb56668d1d0a347d4f0033dd360c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "163ec38d14a35b364e3c9f3c57a7d6aa7f5a178f8ec23a33ce43aab9eed34a9fbb813d8aebb5f91b278dc03061eb9528a457f1e16a449ff0ffc3c7e279be6204"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "bb4e58d3f96691280935c1f6e0b603739878ad6cacc2da6395d75c1e6fb9d2fb2e4de238634ec3bd417621485093e480490002c22114a321affb7b4994714c01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "c7e970795af37c2ec5e73a627daa2fd052d26764b47b70d84257b3607f2e9b4472da8eba5bd720dd4fc9801d9108a69d1bff78d8c0c6b50031bfe158ba1ca300"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "ddb6f6aa044d7b64831b3321d3aa3e3a0c3c1ef5144e9257204d0d7b8e6ac616c9f210166f7f679d8e120d8bdcbc40cfa1fc1a2faf97dbe90b5897a49bd6fe0f"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "7a5b9705304316df3edad16d934518b73a4ee29259e8d5accad5b9fb88c08ae0cd8a95b06f197d94b6ce09cfba528e63eec2464de56ecb5449bba4a2d110a705"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "a5fb7d23197cb5856ba1524676bf4f6bd96181b8f3a992607167f86a4053c0280651894d37667fbdd3ff21ed16a9f61116e969db4532935e79a7723f2c9af101"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "701c686cb7defb91040a58f94d9800f42a142d3efdcc53db84a2c1a89f372d134f631135ffeef676efa1cc8b90b4ebbb5040bfc513a7a2aa54cf087cf9770b06"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/root.json
new file mode 100644
index 0000000..9a443e4
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "dbfb546e3f361f7b746cc076d6632e5fdfe18ec0268b383de317af45f5cb704c9145707144f1f540e91b256b418cf5753de7391fdc1f8ff27c0a6eb265904005"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/targets.json
new file mode 100644
index 0000000..b9ec287
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "8c3d6300986d5859cdbc3d4a3509f4ca3e3e9dda0b9ccfd51ffcae65757f221a478ed0be7dbae9f1c6c1d10063e5eeec1f05b538c282e4f7de1d8970b9fe210c"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2a257763ea72c8d547083ff87438747c4d0f240a24dba479cdddc1bda562a20d929d770262c915d2c5ae79cd3854abebf263cb56668d1d0a347d4f0033dd360c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "163ec38d14a35b364e3c9f3c57a7d6aa7f5a178f8ec23a33ce43aab9eed34a9fbb813d8aebb5f91b278dc03061eb9528a457f1e16a449ff0ffc3c7e279be6204"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "bb4e58d3f96691280935c1f6e0b603739878ad6cacc2da6395d75c1e6fb9d2fb2e4de238634ec3bd417621485093e480490002c22114a321affb7b4994714c01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "c7e970795af37c2ec5e73a627daa2fd052d26764b47b70d84257b3607f2e9b4472da8eba5bd720dd4fc9801d9108a69d1bff78d8c0c6b50031bfe158ba1ca300"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "ddb6f6aa044d7b64831b3321d3aa3e3a0c3c1ef5144e9257204d0d7b8e6ac616c9f210166f7f679d8e120d8bdcbc40cfa1fc1a2faf97dbe90b5897a49bd6fe0f"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "7a5b9705304316df3edad16d934518b73a4ee29259e8d5accad5b9fb88c08ae0cd8a95b06f197d94b6ce09cfba528e63eec2464de56ecb5449bba4a2d110a705"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "a5fb7d23197cb5856ba1524676bf4f6bd96181b8f3a992607167f86a4053c0280651894d37667fbdd3ff21ed16a9f61116e969db4532935e79a7723f2c9af101"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "701c686cb7defb91040a58f94d9800f42a142d3efdcc53db84a2c1a89f372d134f631135ffeef676efa1cc8b90b4ebbb5040bfc513a7a2aa54cf087cf9770b06"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/timestamp.json
new file mode 100644
index 0000000..d32a863
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_targets/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.root.json
new file mode 100644
index 0000000..ace9349
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ae1ce5269bdb49e159d202abd2bf00bc390c6af87e7efe281d3bc1991103fa2a40617d82163c7014616cdf111a37bf4984f7f2282d63625f1c25829f4752b20e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..5347f78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d5e231a96d45560123b3dc33bf2a9043a4b25959c0b84c621060687f41fc047548978be8673a350545653e0093cd38a4432a9f7ef98699b7fb59fc075a227b0a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..6884685
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "a576e0ec5d71e3eec0d3448b5107a0eccd35eeeb148ba5ace712be8979e44cdf3a4df595b76801e19957cd70b72c0783262bc23ca8ffd487c7c97de5f968e300"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:20Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..7bebab1
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/1.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5bb29b3df3004838f4f0d3d0bdcf2e9edcc1859843f129fb54c6dc5da74e43b6c1ba5cd15652d618d5ae9dafae37f0d7a624ae554d2d94d4909425cca1409b07"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "e6216a17a58b31b80fdb6b3bdfb60fb02e35dc15628cb372694715960e0c0ea82028e410852767d511f3b5a7d0953f78d21b004f5fa3eb81da16f1dfe1cc9104"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1b3be15b61bac3a359dcde9ec054f7c4b469c561b2b759e4794ee9535c2ca9defa6d391af04ea108d3cfb0c6e9700b470f115caccdd79db622f73d046b7a8b0d"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "46803209126b583272596e06ef9ce530003f1ca013ee3ef2a77a32afe09d2f9218bcd9016c4c8d947223706f8ca086e01bc0e58a01517c51ef2034a0d8e9f503"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "0bf36823342fe84e5111a887fab0ebb740c8883c3783f68d374f95da6ae142ab9abc1a0be9acdaf6f175fddad15b12a0ecd27fa6a5d7179609468145da1aad01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "27d124a184f4ddaeafe4746a132d119c0e0134a344691eadca7d8847178799d94f41b5f0e3ddeb3f01afab5d9dc8db5d96d0418c18ddec31557306b88d257e0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "34e28fe630dbc1c6c4a67a7775275654262c4e42967a1b99482c903f9171c724b63e95bac7f150d6482ccfd855bd82e1c644e3dd27af4a38afd2040268215100"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "ae3d90d7dd750cf885c78f9c12dbae2d2707f5411b42dd03d3c0bced50952661efc6b05b4f6268a663736c64d9f300d27f3a81d85a93ff5705c310c97058340b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "625c34749f149a2e9183d8efd813da7d8c9ed336a9fb999770290658b9efcc9432551aa85232b3cd994e77ce111265c384216dcc47bbe86c151bb7e14e911907"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/root.json
new file mode 100644
index 0000000..ace9349
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ae1ce5269bdb49e159d202abd2bf00bc390c6af87e7efe281d3bc1991103fa2a40617d82163c7014616cdf111a37bf4984f7f2282d63625f1c25829f4752b20e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..5347f78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d5e231a96d45560123b3dc33bf2a9043a4b25959c0b84c621060687f41fc047548978be8673a350545653e0093cd38a4432a9f7ef98699b7fb59fc075a227b0a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/targets.json
new file mode 100644
index 0000000..6884685
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "a576e0ec5d71e3eec0d3448b5107a0eccd35eeeb148ba5ace712be8979e44cdf3a4df595b76801e19957cd70b72c0783262bc23ca8ffd487c7c97de5f968e300"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:20Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..7bebab1
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/current/timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5bb29b3df3004838f4f0d3d0bdcf2e9edcc1859843f129fb54c6dc5da74e43b6c1ba5cd15652d618d5ae9dafae37f0d7a624ae554d2d94d4909425cca1409b07"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "e6216a17a58b31b80fdb6b3bdfb60fb02e35dc15628cb372694715960e0c0ea82028e410852767d511f3b5a7d0953f78d21b004f5fa3eb81da16f1dfe1cc9104"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1b3be15b61bac3a359dcde9ec054f7c4b469c561b2b759e4794ee9535c2ca9defa6d391af04ea108d3cfb0c6e9700b470f115caccdd79db622f73d046b7a8b0d"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "46803209126b583272596e06ef9ce530003f1ca013ee3ef2a77a32afe09d2f9218bcd9016c4c8d947223706f8ca086e01bc0e58a01517c51ef2034a0d8e9f503"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "0bf36823342fe84e5111a887fab0ebb740c8883c3783f68d374f95da6ae142ab9abc1a0be9acdaf6f175fddad15b12a0ecd27fa6a5d7179609468145da1aad01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "27d124a184f4ddaeafe4746a132d119c0e0134a344691eadca7d8847178799d94f41b5f0e3ddeb3f01afab5d9dc8db5d96d0418c18ddec31557306b88d257e0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "34e28fe630dbc1c6c4a67a7775275654262c4e42967a1b99482c903f9171c724b63e95bac7f150d6482ccfd855bd82e1c644e3dd27af4a38afd2040268215100"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "ae3d90d7dd750cf885c78f9c12dbae2d2707f5411b42dd03d3c0bced50952661efc6b05b4f6268a663736c64d9f300d27f3a81d85a93ff5705c310c97058340b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "625c34749f149a2e9183d8efd813da7d8c9ed336a9fb999770290658b9efcc9432551aa85232b3cd994e77ce111265c384216dcc47bbe86c151bb7e14e911907"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..ace9349
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ae1ce5269bdb49e159d202abd2bf00bc390c6af87e7efe281d3bc1991103fa2a40617d82163c7014616cdf111a37bf4984f7f2282d63625f1c25829f4752b20e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..5347f78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d5e231a96d45560123b3dc33bf2a9043a4b25959c0b84c621060687f41fc047548978be8673a350545653e0093cd38a4432a9f7ef98699b7fb59fc075a227b0a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..6884685
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "a576e0ec5d71e3eec0d3448b5107a0eccd35eeeb148ba5ace712be8979e44cdf3a4df595b76801e19957cd70b72c0783262bc23ca8ffd487c7c97de5f968e300"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:20Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..7bebab1
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5bb29b3df3004838f4f0d3d0bdcf2e9edcc1859843f129fb54c6dc5da74e43b6c1ba5cd15652d618d5ae9dafae37f0d7a624ae554d2d94d4909425cca1409b07"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "e6216a17a58b31b80fdb6b3bdfb60fb02e35dc15628cb372694715960e0c0ea82028e410852767d511f3b5a7d0953f78d21b004f5fa3eb81da16f1dfe1cc9104"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1b3be15b61bac3a359dcde9ec054f7c4b469c561b2b759e4794ee9535c2ca9defa6d391af04ea108d3cfb0c6e9700b470f115caccdd79db622f73d046b7a8b0d"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "46803209126b583272596e06ef9ce530003f1ca013ee3ef2a77a32afe09d2f9218bcd9016c4c8d947223706f8ca086e01bc0e58a01517c51ef2034a0d8e9f503"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "0bf36823342fe84e5111a887fab0ebb740c8883c3783f68d374f95da6ae142ab9abc1a0be9acdaf6f175fddad15b12a0ecd27fa6a5d7179609468145da1aad01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "27d124a184f4ddaeafe4746a132d119c0e0134a344691eadca7d8847178799d94f41b5f0e3ddeb3f01afab5d9dc8db5d96d0418c18ddec31557306b88d257e0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "34e28fe630dbc1c6c4a67a7775275654262c4e42967a1b99482c903f9171c724b63e95bac7f150d6482ccfd855bd82e1c644e3dd27af4a38afd2040268215100"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "ae3d90d7dd750cf885c78f9c12dbae2d2707f5411b42dd03d3c0bced50952661efc6b05b4f6268a663736c64d9f300d27f3a81d85a93ff5705c310c97058340b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "625c34749f149a2e9183d8efd813da7d8c9ed336a9fb999770290658b9efcc9432551aa85232b3cd994e77ce111265c384216dcc47bbe86c151bb7e14e911907"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/root.json
new file mode 100644
index 0000000..ace9349
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ae1ce5269bdb49e159d202abd2bf00bc390c6af87e7efe281d3bc1991103fa2a40617d82163c7014616cdf111a37bf4984f7f2282d63625f1c25829f4752b20e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..5347f78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d5e231a96d45560123b3dc33bf2a9043a4b25959c0b84c621060687f41fc047548978be8673a350545653e0093cd38a4432a9f7ef98699b7fb59fc075a227b0a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/targets.json
new file mode 100644
index 0000000..6884685
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "a576e0ec5d71e3eec0d3448b5107a0eccd35eeeb148ba5ace712be8979e44cdf3a4df595b76801e19957cd70b72c0783262bc23ca8ffd487c7c97de5f968e300"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:20Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..7bebab1
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/client/metadata/previous/timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5bb29b3df3004838f4f0d3d0bdcf2e9edcc1859843f129fb54c6dc5da74e43b6c1ba5cd15652d618d5ae9dafae37f0d7a624ae554d2d94d4909425cca1409b07"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "e6216a17a58b31b80fdb6b3bdfb60fb02e35dc15628cb372694715960e0c0ea82028e410852767d511f3b5a7d0953f78d21b004f5fa3eb81da16f1dfe1cc9104"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1b3be15b61bac3a359dcde9ec054f7c4b469c561b2b759e4794ee9535c2ca9defa6d391af04ea108d3cfb0c6e9700b470f115caccdd79db622f73d046b7a8b0d"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "46803209126b583272596e06ef9ce530003f1ca013ee3ef2a77a32afe09d2f9218bcd9016c4c8d947223706f8ca086e01bc0e58a01517c51ef2034a0d8e9f503"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "0bf36823342fe84e5111a887fab0ebb740c8883c3783f68d374f95da6ae142ab9abc1a0be9acdaf6f175fddad15b12a0ecd27fa6a5d7179609468145da1aad01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "27d124a184f4ddaeafe4746a132d119c0e0134a344691eadca7d8847178799d94f41b5f0e3ddeb3f01afab5d9dc8db5d96d0418c18ddec31557306b88d257e0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "34e28fe630dbc1c6c4a67a7775275654262c4e42967a1b99482c903f9171c724b63e95bac7f150d6482ccfd855bd82e1c644e3dd27af4a38afd2040268215100"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "ae3d90d7dd750cf885c78f9c12dbae2d2707f5411b42dd03d3c0bced50952661efc6b05b4f6268a663736c64d9f300d27f3a81d85a93ff5705c310c97058340b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "625c34749f149a2e9183d8efd813da7d8c9ed336a9fb999770290658b9efcc9432551aa85232b3cd994e77ce111265c384216dcc47bbe86c151bb7e14e911907"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/hash.txt b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/hash.txt
new file mode 100644
index 0000000..49d284e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/hash.txt
@@ -0,0 +1 @@
+19c5ec00c484505be65a024696fd980add6848b623d74d7eee92dbd3b2c8f178 \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..ace9349
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ae1ce5269bdb49e159d202abd2bf00bc390c6af87e7efe281d3bc1991103fa2a40617d82163c7014616cdf111a37bf4984f7f2282d63625f1c25829f4752b20e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..5347f78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d5e231a96d45560123b3dc33bf2a9043a4b25959c0b84c621060687f41fc047548978be8673a350545653e0093cd38a4432a9f7ef98699b7fb59fc075a227b0a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..6884685
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "a576e0ec5d71e3eec0d3448b5107a0eccd35eeeb148ba5ace712be8979e44cdf3a4df595b76801e19957cd70b72c0783262bc23ca8ffd487c7c97de5f968e300"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:20Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..7bebab1
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5bb29b3df3004838f4f0d3d0bdcf2e9edcc1859843f129fb54c6dc5da74e43b6c1ba5cd15652d618d5ae9dafae37f0d7a624ae554d2d94d4909425cca1409b07"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "e6216a17a58b31b80fdb6b3bdfb60fb02e35dc15628cb372694715960e0c0ea82028e410852767d511f3b5a7d0953f78d21b004f5fa3eb81da16f1dfe1cc9104"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1b3be15b61bac3a359dcde9ec054f7c4b469c561b2b759e4794ee9535c2ca9defa6d391af04ea108d3cfb0c6e9700b470f115caccdd79db622f73d046b7a8b0d"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "46803209126b583272596e06ef9ce530003f1ca013ee3ef2a77a32afe09d2f9218bcd9016c4c8d947223706f8ca086e01bc0e58a01517c51ef2034a0d8e9f503"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "0bf36823342fe84e5111a887fab0ebb740c8883c3783f68d374f95da6ae142ab9abc1a0be9acdaf6f175fddad15b12a0ecd27fa6a5d7179609468145da1aad01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "27d124a184f4ddaeafe4746a132d119c0e0134a344691eadca7d8847178799d94f41b5f0e3ddeb3f01afab5d9dc8db5d96d0418c18ddec31557306b88d257e0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "34e28fe630dbc1c6c4a67a7775275654262c4e42967a1b99482c903f9171c724b63e95bac7f150d6482ccfd855bd82e1c644e3dd27af4a38afd2040268215100"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "ae3d90d7dd750cf885c78f9c12dbae2d2707f5411b42dd03d3c0bced50952661efc6b05b4f6268a663736c64d9f300d27f3a81d85a93ff5705c310c97058340b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "625c34749f149a2e9183d8efd813da7d8c9ed336a9fb999770290658b9efcc9432551aa85232b3cd994e77ce111265c384216dcc47bbe86c151bb7e14e911907"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..ce76c3c
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/2.root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "49f17c01fd9e296f1029144b2c7cff78672f8f0d6f2c6c2049e6412a21f33c8f993fd3e8faf8debefdb4af1a2e2c530826398b8f0c2b1bc9d1dbe3d30d20e60a"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/2.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/2.timestamp.json
new file mode 100644
index 0000000..8ca738c
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/2.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "4bf1ce4b2a66f6f4bb002d06f6d69a3b72678bcf0ce0d16ac4eaf8faa6eb35aba57cb368abb86f81b41c60dc59c0a43bdbee6fcc3ae5be381aad5aec0cc0e80c"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f0bbf3b300d72231df1c4bb55e73e20afa08a32f8414ad66d76c886384a71f23b64301fa3f5ebaf30cddd33407ffe58ae00c08112ea30e4f18f355cd4e2bd70d"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "721f21d83e4e22e52da0f4ec76e05a16f2f6ac41e5f688b5ea81c16a2b3d0be44dab1df306a100a826fc10f2991eb6ba295ff0dd7269abd1faa9e57140fc4508"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0d1c192b314eab6296eec926b34a9393e27432fbe9d0ac349384648236138c9a2f4c5cbde14074e1c58ce92247f67d93c4c55d6cbfda0873f285b4922e614003"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "d333eccfaadee6bf457743270df4587550c090b32285ab1cea64b9829e6455218c01bbb6848a4e9126b54b5c893121a1c43d2bf5229165396f79d621d0ba7806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "ee798279d0e2a7afc94794e99ab17ce8b5754be06b1e6108e5d380b70950e062d5f4551e261545eeba3845d779f51b1390cc6a535b8a0e5a6db083e648bc060c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5381bd588a78bf1820e284f6b403696fe56b786df783522d85a0f42e6f3750ca8dd9c6c6dc9a81a40471c97ed50be24555b2f6b7a1439e25e31297342ccfe10c"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "400dd1ee64a31bf899f956a9d43710ff9b96cd3737a528fd0ae5ef313a91ba4132ee09dc2ed4b77868b09811b9bc8b7808d4eab06df5b57c16fdb53ac2d9fa0b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e53225418b7b6edd22970bf3e3ed1dd2931d7b8c7ab1e91f54efa12554b151d80c3591f0203eba317873c5aa71bfec5c5349b43ca8a5143a3b393b4751c500b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/root.json
new file mode 100644
index 0000000..ce76c3c
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "49f17c01fd9e296f1029144b2c7cff78672f8f0d6f2c6c2049e6412a21f33c8f993fd3e8faf8debefdb4af1a2e2c530826398b8f0c2b1bc9d1dbe3d30d20e60a"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..5347f78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d5e231a96d45560123b3dc33bf2a9043a4b25959c0b84c621060687f41fc047548978be8673a350545653e0093cd38a4432a9f7ef98699b7fb59fc075a227b0a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/targets.json
new file mode 100644
index 0000000..6884685
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "a576e0ec5d71e3eec0d3448b5107a0eccd35eeeb148ba5ace712be8979e44cdf3a4df595b76801e19957cd70b72c0783262bc23ca8ffd487c7c97de5f968e300"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:20Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..8ca738c
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata.staged/timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "4bf1ce4b2a66f6f4bb002d06f6d69a3b72678bcf0ce0d16ac4eaf8faa6eb35aba57cb368abb86f81b41c60dc59c0a43bdbee6fcc3ae5be381aad5aec0cc0e80c"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f0bbf3b300d72231df1c4bb55e73e20afa08a32f8414ad66d76c886384a71f23b64301fa3f5ebaf30cddd33407ffe58ae00c08112ea30e4f18f355cd4e2bd70d"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "721f21d83e4e22e52da0f4ec76e05a16f2f6ac41e5f688b5ea81c16a2b3d0be44dab1df306a100a826fc10f2991eb6ba295ff0dd7269abd1faa9e57140fc4508"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0d1c192b314eab6296eec926b34a9393e27432fbe9d0ac349384648236138c9a2f4c5cbde14074e1c58ce92247f67d93c4c55d6cbfda0873f285b4922e614003"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "d333eccfaadee6bf457743270df4587550c090b32285ab1cea64b9829e6455218c01bbb6848a4e9126b54b5c893121a1c43d2bf5229165396f79d621d0ba7806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "ee798279d0e2a7afc94794e99ab17ce8b5754be06b1e6108e5d380b70950e062d5f4551e261545eeba3845d779f51b1390cc6a535b8a0e5a6db083e648bc060c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5381bd588a78bf1820e284f6b403696fe56b786df783522d85a0f42e6f3750ca8dd9c6c6dc9a81a40471c97ed50be24555b2f6b7a1439e25e31297342ccfe10c"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "400dd1ee64a31bf899f956a9d43710ff9b96cd3737a528fd0ae5ef313a91ba4132ee09dc2ed4b77868b09811b9bc8b7808d4eab06df5b57c16fdb53ac2d9fa0b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e53225418b7b6edd22970bf3e3ed1dd2931d7b8c7ab1e91f54efa12554b151d80c3591f0203eba317873c5aa71bfec5c5349b43ca8a5143a3b393b4751c500b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.root.json
new file mode 100644
index 0000000..ace9349
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ae1ce5269bdb49e159d202abd2bf00bc390c6af87e7efe281d3bc1991103fa2a40617d82163c7014616cdf111a37bf4984f7f2282d63625f1c25829f4752b20e"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..5347f78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d5e231a96d45560123b3dc33bf2a9043a4b25959c0b84c621060687f41fc047548978be8673a350545653e0093cd38a4432a9f7ef98699b7fb59fc075a227b0a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.targets.json
new file mode 100644
index 0000000..6884685
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "a576e0ec5d71e3eec0d3448b5107a0eccd35eeeb148ba5ace712be8979e44cdf3a4df595b76801e19957cd70b72c0783262bc23ca8ffd487c7c97de5f968e300"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:20Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..7bebab1
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/1.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "5bb29b3df3004838f4f0d3d0bdcf2e9edcc1859843f129fb54c6dc5da74e43b6c1ba5cd15652d618d5ae9dafae37f0d7a624ae554d2d94d4909425cca1409b07"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "e6216a17a58b31b80fdb6b3bdfb60fb02e35dc15628cb372694715960e0c0ea82028e410852767d511f3b5a7d0953f78d21b004f5fa3eb81da16f1dfe1cc9104"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "1b3be15b61bac3a359dcde9ec054f7c4b469c561b2b759e4794ee9535c2ca9defa6d391af04ea108d3cfb0c6e9700b470f115caccdd79db622f73d046b7a8b0d"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "46803209126b583272596e06ef9ce530003f1ca013ee3ef2a77a32afe09d2f9218bcd9016c4c8d947223706f8ca086e01bc0e58a01517c51ef2034a0d8e9f503"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "0bf36823342fe84e5111a887fab0ebb740c8883c3783f68d374f95da6ae142ab9abc1a0be9acdaf6f175fddad15b12a0ecd27fa6a5d7179609468145da1aad01"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "27d124a184f4ddaeafe4746a132d119c0e0134a344691eadca7d8847178799d94f41b5f0e3ddeb3f01afab5d9dc8db5d96d0418c18ddec31557306b88d257e0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "34e28fe630dbc1c6c4a67a7775275654262c4e42967a1b99482c903f9171c724b63e95bac7f150d6482ccfd855bd82e1c644e3dd27af4a38afd2040268215100"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "ae3d90d7dd750cf885c78f9c12dbae2d2707f5411b42dd03d3c0bced50952661efc6b05b4f6268a663736c64d9f300d27f3a81d85a93ff5705c310c97058340b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "625c34749f149a2e9183d8efd813da7d8c9ed336a9fb999770290658b9efcc9432551aa85232b3cd994e77ce111265c384216dcc47bbe86c151bb7e14e911907"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/2.root.json
new file mode 100644
index 0000000..ce76c3c
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/2.root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "49f17c01fd9e296f1029144b2c7cff78672f8f0d6f2c6c2049e6412a21f33c8f993fd3e8faf8debefdb4af1a2e2c530826398b8f0c2b1bc9d1dbe3d30d20e60a"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/2.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/2.timestamp.json
new file mode 100644
index 0000000..8ca738c
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/2.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "4bf1ce4b2a66f6f4bb002d06f6d69a3b72678bcf0ce0d16ac4eaf8faa6eb35aba57cb368abb86f81b41c60dc59c0a43bdbee6fcc3ae5be381aad5aec0cc0e80c"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f0bbf3b300d72231df1c4bb55e73e20afa08a32f8414ad66d76c886384a71f23b64301fa3f5ebaf30cddd33407ffe58ae00c08112ea30e4f18f355cd4e2bd70d"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "721f21d83e4e22e52da0f4ec76e05a16f2f6ac41e5f688b5ea81c16a2b3d0be44dab1df306a100a826fc10f2991eb6ba295ff0dd7269abd1faa9e57140fc4508"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0d1c192b314eab6296eec926b34a9393e27432fbe9d0ac349384648236138c9a2f4c5cbde14074e1c58ce92247f67d93c4c55d6cbfda0873f285b4922e614003"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "d333eccfaadee6bf457743270df4587550c090b32285ab1cea64b9829e6455218c01bbb6848a4e9126b54b5c893121a1c43d2bf5229165396f79d621d0ba7806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "ee798279d0e2a7afc94794e99ab17ce8b5754be06b1e6108e5d380b70950e062d5f4551e261545eeba3845d779f51b1390cc6a535b8a0e5a6db083e648bc060c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5381bd588a78bf1820e284f6b403696fe56b786df783522d85a0f42e6f3750ca8dd9c6c6dc9a81a40471c97ed50be24555b2f6b7a1439e25e31297342ccfe10c"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "400dd1ee64a31bf899f956a9d43710ff9b96cd3737a528fd0ae5ef313a91ba4132ee09dc2ed4b77868b09811b9bc8b7808d4eab06df5b57c16fdb53ac2d9fa0b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e53225418b7b6edd22970bf3e3ed1dd2931d7b8c7ab1e91f54efa12554b151d80c3591f0203eba317873c5aa71bfec5c5349b43ca8a5143a3b393b4751c500b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/root.json
new file mode 100644
index 0000000..ce76c3c
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/root.json
@@ -0,0 +1,159 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "49f17c01fd9e296f1029144b2c7cff78672f8f0d6f2c6c2049e6412a21f33c8f993fd3e8faf8debefdb4af1a2e2c530826398b8f0c2b1bc9d1dbe3d30d20e60a"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:20Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/snapshot.json
new file mode 100644
index 0000000..5347f78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "d5e231a96d45560123b3dc33bf2a9043a4b25959c0b84c621060687f41fc047548978be8673a350545653e0093cd38a4432a9f7ef98699b7fb59fc075a227b0a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/targets.json
new file mode 100644
index 0000000..6884685
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "a576e0ec5d71e3eec0d3448b5107a0eccd35eeeb148ba5ace712be8979e44cdf3a4df595b76801e19957cd70b72c0783262bc23ca8ffd487c7c97de5f968e300"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:20Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/timestamp.json
new file mode 100644
index 0000000..8ca738c
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_2_threshold_4_timestamp/server/metadata/timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "4bf1ce4b2a66f6f4bb002d06f6d69a3b72678bcf0ce0d16ac4eaf8faa6eb35aba57cb368abb86f81b41c60dc59c0a43bdbee6fcc3ae5be381aad5aec0cc0e80c"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f0bbf3b300d72231df1c4bb55e73e20afa08a32f8414ad66d76c886384a71f23b64301fa3f5ebaf30cddd33407ffe58ae00c08112ea30e4f18f355cd4e2bd70d"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "721f21d83e4e22e52da0f4ec76e05a16f2f6ac41e5f688b5ea81c16a2b3d0be44dab1df306a100a826fc10f2991eb6ba295ff0dd7269abd1faa9e57140fc4508"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "0d1c192b314eab6296eec926b34a9393e27432fbe9d0ac349384648236138c9a2f4c5cbde14074e1c58ce92247f67d93c4c55d6cbfda0873f285b4922e614003"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "d333eccfaadee6bf457743270df4587550c090b32285ab1cea64b9829e6455218c01bbb6848a4e9126b54b5c893121a1c43d2bf5229165396f79d621d0ba7806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "ee798279d0e2a7afc94794e99ab17ce8b5754be06b1e6108e5d380b70950e062d5f4551e261545eeba3845d779f51b1390cc6a535b8a0e5a6db083e648bc060c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5381bd588a78bf1820e284f6b403696fe56b786df783522d85a0f42e6f3750ca8dd9c6c6dc9a81a40471c97ed50be24555b2f6b7a1439e25e31297342ccfe10c"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "400dd1ee64a31bf899f956a9d43710ff9b96cd3737a528fd0ae5ef313a91ba4132ee09dc2ed4b77868b09811b9bc8b7808d4eab06df5b57c16fdb53ac2d9fa0b"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e53225418b7b6edd22970bf3e3ed1dd2931d7b8c7ab1e91f54efa12554b151d80c3591f0203eba317873c5aa71bfec5c5349b43ca8a5143a3b393b4751c500b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:20Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "7a797f95dcb2e1894c4b2f59ec6c596dff205c16d23060a5fda55a0f053be2bd",
+ "sha512": "d7d872b82f550d0a0b0458fb15ab343b66d9c6a07c0915ef1a1cf903ec303b717b56c04013b64599a64171564825ca01314a65c690eafecd088a3881bf62c278"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/hash.txt b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/hash.txt
new file mode 100644
index 0000000..f594895
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/hash.txt
@@ -0,0 +1 @@
+3394568b65c89ced0502b93667ebc005aa9337c95f1c10c8e0f09c002b217ede \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..06509ad
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/2.root.json
@@ -0,0 +1,167 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "1796c3a9956f553c32ff9c7db8734af9d960989f89f185ef0edc36a87591d2e815ef84a7903bd553ec7ed156170e0c701739bdb9c8fd61316b36dd870525b30a"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "1aae88766ce5015d6ec2e75345a8ed5ac7b7e7a3c234e7f2215c37e577bb512743f612f6d7ead301970f1a01dbd15a7a4467147823a1e226ce5643817fafb900"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "7f7bec7da5b5b88bd33ac8c94dbee5700c6fdae5454a98a21516edea8a8d5714c16d49957b3eebbb4b88aa3f3e2e17590f2b056894cb1093c7399a403ad33007"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "398ab57bf00e675ccf5c6a34c88013f86488cece97646db6e00aac40ca3e2f68c8ee6e2d73a14ee9484cb6405f1f653ff896ff95b062221c0c36df2a71b5e50f"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "8ea937bb5a9451161b32605676731ded282d3878e8058d585759b43ee1700a77a33d1b21614486e0240fc1d0af39723cc85ea403479d6efb62c05cc6fdd16a08"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf47e74acfdc137847a1c96d7ef42e3049a347cf96c6b873e5047dd2e50050e13a0dba4907138ba3c29e1c768ddfc6332647fb9658fede4229192e1bc5b6d409"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "9c17a9c043a6dc378334f162999767acc012f6cb33e041f3c9b14963cacb1a32b6cba552931eb9ddd8bd78bf0a872498a8829c4c6c261a2978b083a8639c110f"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "c5698a3a2195c4399b4e04fc9db778a7c02acf82c691517fad43a387083fa4dfe9331c1429c10d22ca14088516518939b104994c68dfc16d21b1ada46ef7b700"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "78f401be103e238daf3c931c2eeb6e7a0eb118042bcbabce533a4d0057e9a19b7df5357fd8f837f36d58a69b1a2719ab2e326e4656e92e8435dd14ba2b01640f"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/root.json
new file mode 100644
index 0000000..06509ad
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/root.json
@@ -0,0 +1,167 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "1796c3a9956f553c32ff9c7db8734af9d960989f89f185ef0edc36a87591d2e815ef84a7903bd553ec7ed156170e0c701739bdb9c8fd61316b36dd870525b30a"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "1aae88766ce5015d6ec2e75345a8ed5ac7b7e7a3c234e7f2215c37e577bb512743f612f6d7ead301970f1a01dbd15a7a4467147823a1e226ce5643817fafb900"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "7f7bec7da5b5b88bd33ac8c94dbee5700c6fdae5454a98a21516edea8a8d5714c16d49957b3eebbb4b88aa3f3e2e17590f2b056894cb1093c7399a403ad33007"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "398ab57bf00e675ccf5c6a34c88013f86488cece97646db6e00aac40ca3e2f68c8ee6e2d73a14ee9484cb6405f1f653ff896ff95b062221c0c36df2a71b5e50f"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "8ea937bb5a9451161b32605676731ded282d3878e8058d585759b43ee1700a77a33d1b21614486e0240fc1d0af39723cc85ea403479d6efb62c05cc6fdd16a08"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf47e74acfdc137847a1c96d7ef42e3049a347cf96c6b873e5047dd2e50050e13a0dba4907138ba3c29e1c768ddfc6332647fb9658fede4229192e1bc5b6d409"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "9c17a9c043a6dc378334f162999767acc012f6cb33e041f3c9b14963cacb1a32b6cba552931eb9ddd8bd78bf0a872498a8829c4c6c261a2978b083a8639c110f"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "c5698a3a2195c4399b4e04fc9db778a7c02acf82c691517fad43a387083fa4dfe9331c1429c10d22ca14088516518939b104994c68dfc16d21b1ada46ef7b700"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "78f401be103e238daf3c931c2eeb6e7a0eb118042bcbabce533a4d0057e9a19b7df5357fd8f837f36d58a69b1a2719ab2e326e4656e92e8435dd14ba2b01640f"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.root.json
new file mode 100644
index 0000000..f277106
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.root.json
@@ -0,0 +1,215 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "876195f83e8dbb1b00f70188fc7f405d05d321da97a9b98781f331d2dea6be1415d0a0b4e23697f2e30235e98c3503a2ec3aade3bd4c1bac8109f368d9ed5005"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "ff3e54a7492085d33ebf25158ce673fdd7d871a49cdccbe31b35c9ba1c28b91fd22b161b8a226248b3ef3e81378cdaa1ef3e37c2100363ec1be6d87d19a2aa0c"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "59da18183f6729135b74539865f55e5c6e3d837209b724922cd56dd79f46c7150016930f477707c78ee9e39ee0f1a8e18304ab4cdaf1c11f81f147cbf5c57904"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "c0b98cba687cdab0628872207026501c9b3c289af113c0be83176a908e58a647b2be9716b37ae4acc78327bbcd715d97d200953681646f3bc918e6c0f409dc0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "7fa4506b7e9c6aac34b6c9b5e1099e8fa995fb1af7804cb9245b0132edf41739b7049277de11577c1ff72c1cf8fe41ba3ec32bf802cf16cb19e42765f53d3f0e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "8c5af6fd1930026a1a9d9816e18205dc4e6bbf0e4b9dd7385d9cc10ca6bf3609cce7c62d5ea5432e0a67e3c395531cf1c67f874f0ba1acb27bf161423b34500c"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ef03e8b766ffe280b1486460ee1b8cd92e6605d4d4af32696a0345b940767f7e3ae66f217b97eae12aa704fa5ee03073079046efe4c76c14989c4784d2d1fa0d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "0a536f66722b82fa8da8ef6837cdae1d719abe72dd27fe36a4fcb2d21302989bb897275825bfef3a5f93fd487f947c75c703da69ad67988c1be46a975a8a6602"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "caf6083cc4ab3fc5a39cc243d08faf0724435b9189b4a3349cb8955b321d9c644697c9d96698e09a5c47f5d032a562c97b08a3e840dbb60f4224afeb438a3a0b"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/2.root.json
new file mode 100644
index 0000000..06509ad
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/2.root.json
@@ -0,0 +1,167 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "1796c3a9956f553c32ff9c7db8734af9d960989f89f185ef0edc36a87591d2e815ef84a7903bd553ec7ed156170e0c701739bdb9c8fd61316b36dd870525b30a"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "1aae88766ce5015d6ec2e75345a8ed5ac7b7e7a3c234e7f2215c37e577bb512743f612f6d7ead301970f1a01dbd15a7a4467147823a1e226ce5643817fafb900"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "7f7bec7da5b5b88bd33ac8c94dbee5700c6fdae5454a98a21516edea8a8d5714c16d49957b3eebbb4b88aa3f3e2e17590f2b056894cb1093c7399a403ad33007"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "398ab57bf00e675ccf5c6a34c88013f86488cece97646db6e00aac40ca3e2f68c8ee6e2d73a14ee9484cb6405f1f653ff896ff95b062221c0c36df2a71b5e50f"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "8ea937bb5a9451161b32605676731ded282d3878e8058d585759b43ee1700a77a33d1b21614486e0240fc1d0af39723cc85ea403479d6efb62c05cc6fdd16a08"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf47e74acfdc137847a1c96d7ef42e3049a347cf96c6b873e5047dd2e50050e13a0dba4907138ba3c29e1c768ddfc6332647fb9658fede4229192e1bc5b6d409"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "9c17a9c043a6dc378334f162999767acc012f6cb33e041f3c9b14963cacb1a32b6cba552931eb9ddd8bd78bf0a872498a8829c4c6c261a2978b083a8639c110f"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "c5698a3a2195c4399b4e04fc9db778a7c02acf82c691517fad43a387083fa4dfe9331c1429c10d22ca14088516518939b104994c68dfc16d21b1ada46ef7b700"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "78f401be103e238daf3c931c2eeb6e7a0eb118042bcbabce533a4d0057e9a19b7df5357fd8f837f36d58a69b1a2719ab2e326e4656e92e8435dd14ba2b01640f"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/root.json
new file mode 100644
index 0000000..06509ad
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/root.json
@@ -0,0 +1,167 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "1796c3a9956f553c32ff9c7db8734af9d960989f89f185ef0edc36a87591d2e815ef84a7903bd553ec7ed156170e0c701739bdb9c8fd61316b36dd870525b30a"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "1aae88766ce5015d6ec2e75345a8ed5ac7b7e7a3c234e7f2215c37e577bb512743f612f6d7ead301970f1a01dbd15a7a4467147823a1e226ce5643817fafb900"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "7f7bec7da5b5b88bd33ac8c94dbee5700c6fdae5454a98a21516edea8a8d5714c16d49957b3eebbb4b88aa3f3e2e17590f2b056894cb1093c7399a403ad33007"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "398ab57bf00e675ccf5c6a34c88013f86488cece97646db6e00aac40ca3e2f68c8ee6e2d73a14ee9484cb6405f1f653ff896ff95b062221c0c36df2a71b5e50f"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "8ea937bb5a9451161b32605676731ded282d3878e8058d585759b43ee1700a77a33d1b21614486e0240fc1d0af39723cc85ea403479d6efb62c05cc6fdd16a08"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "bf47e74acfdc137847a1c96d7ef42e3049a347cf96c6b873e5047dd2e50050e13a0dba4907138ba3c29e1c768ddfc6332647fb9658fede4229192e1bc5b6d409"
+ },
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "9c17a9c043a6dc378334f162999767acc012f6cb33e041f3c9b14963cacb1a32b6cba552931eb9ddd8bd78bf0a872498a8829c4c6c261a2978b083a8639c110f"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "c5698a3a2195c4399b4e04fc9db778a7c02acf82c691517fad43a387083fa4dfe9331c1429c10d22ca14088516518939b104994c68dfc16d21b1ada46ef7b700"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "78f401be103e238daf3c931c2eeb6e7a0eb118042bcbabce533a4d0057e9a19b7df5357fd8f837f36d58a69b1a2719ab2e326e4656e92e8435dd14ba2b01640f"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:18Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/snapshot.json
new file mode 100644
index 0000000..98ee47e
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7945d5bbebfd0f077a6f8bcac4eed402709f07307663b935835712ff4fb4eebbc51146039d0a1ecb3bc7726c526f12e0b063cc3d9dae1c422179db99d4409b0c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/targets.json
new file mode 100644
index 0000000..210307f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "1c07f726a8988b2bbb150cd91c36fa2e0716eb904a89002e24f2382641d5f7680e04cbedbecf89034c8b6416b7a31d5c4bdcc20f0aa2cd976353828cda0e3e0e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:18Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/timestamp.json
new file mode 100644
index 0000000..f5c7c18
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_root/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "18e08cd54878e2af5838ed4445230e687ce0007c1b886b3c1446ee639365b0053e294c930b176fdb1258af9f957e4c4927beaccbbffb354cc27b9d5745257808"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:18Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "765e1bc98ad6235b9a6e1c200e8576c262ed924ba81a9960268a65b7fc1bba91",
+ "sha512": "b245cd1eb39623b7d6d9d07765bae5e7a373471c4bfc66862042e50f0ced2fbe564dfd23565ce7746e926492d6543c700b22ce48bbe19f5e4128698d243cea49"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/hash.txt b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/hash.txt
new file mode 100644
index 0000000..b0c267a
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/hash.txt
@@ -0,0 +1 @@
+af9b3cfe7cc8b819338f9cf29b6d297df3c3b5969d7fd08fd5f0d945932051e6 \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..fe8ada3
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/2.root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e74e989e4ef342e273a7838703998da620fe1585a53bc9b0700c458acd83d82a8e35d69c9ef448b0ab591da6dbfc22341c950dd32ffbb1f525923c34b9047704"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/2.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/2.snapshot.json
new file mode 100644
index 0000000..7cb7305
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/2.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "25cd6eee4f580fed660330de02cb9ab34b9e7495d3f4c57babf225c3c2c39a0937afe633a7760c19681c6b05d89f2c3907a7eef32822992624223cfbcb872d00"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2011938c47cf0990172acc5a418b9758d124655200b6874fb6646bd6025de6c5a48a3c30ced83b1f955b242c1c3f4d2d346b71861fccdc45c945418aa2511d0e"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "303b7b1062a09644bd18804b6b1af6818c25abd212b6539cab0fab0e729a4eb14621396fde4d809649c37b82db5278e878b21b52052ac4fecbcff471c8f64408"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "85961c73d21ab17e5892681198479700a995920cd02001818979fe35122e5776fcc3fd499811b1f995f922ad737d61c5b72229ab1e4d7aab63d9ea1642795c0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "4aed1e9ef8d1ac785d15df386470df92ff484f71c35a37ecd1e83e0fd66e67206172f6a2f277bdebae17372e75d253b45143adf3f9d8b6d23a4690d482e2180b"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "26ed17502d130ab4c7b26a1ef78730d9f15847cc3aa6623f692003e588554761b6f6c3b60da304933b3e012478466ce634970562504fdab9b6d2c8e818f3220e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "0b498cf7cd3a926007f158d9b72002283d67f4f0c2e4a61d5bb3d3afe09d0e9dcd41a4bc67d5cc87a2d072028904d2e022ca4d2abc84137433cdd6812f613505"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1f9aea75e9b632f06769ac0aa611ce3116faa14a3736cddc4cdbcae5f7084a0cd44c4a44a8c78a4200f34b097c0176c8c686b352c79105ffcfd764473f9d3206"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3f315ca76675299894f1ad111dcce7450e1e2f06400e64a86db5d42c67a14fc7a8f92b1063ae547229544d046bcbb038027e0e0abab05bfb6600f7349a9bf90a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/root.json
new file mode 100644
index 0000000..fe8ada3
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e74e989e4ef342e273a7838703998da620fe1585a53bc9b0700c458acd83d82a8e35d69c9ef448b0ab591da6dbfc22341c950dd32ffbb1f525923c34b9047704"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..7cb7305
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "25cd6eee4f580fed660330de02cb9ab34b9e7495d3f4c57babf225c3c2c39a0937afe633a7760c19681c6b05d89f2c3907a7eef32822992624223cfbcb872d00"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2011938c47cf0990172acc5a418b9758d124655200b6874fb6646bd6025de6c5a48a3c30ced83b1f955b242c1c3f4d2d346b71861fccdc45c945418aa2511d0e"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "303b7b1062a09644bd18804b6b1af6818c25abd212b6539cab0fab0e729a4eb14621396fde4d809649c37b82db5278e878b21b52052ac4fecbcff471c8f64408"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "85961c73d21ab17e5892681198479700a995920cd02001818979fe35122e5776fcc3fd499811b1f995f922ad737d61c5b72229ab1e4d7aab63d9ea1642795c0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "4aed1e9ef8d1ac785d15df386470df92ff484f71c35a37ecd1e83e0fd66e67206172f6a2f277bdebae17372e75d253b45143adf3f9d8b6d23a4690d482e2180b"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "26ed17502d130ab4c7b26a1ef78730d9f15847cc3aa6623f692003e588554761b6f6c3b60da304933b3e012478466ce634970562504fdab9b6d2c8e818f3220e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "0b498cf7cd3a926007f158d9b72002283d67f4f0c2e4a61d5bb3d3afe09d0e9dcd41a4bc67d5cc87a2d072028904d2e022ca4d2abc84137433cdd6812f613505"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1f9aea75e9b632f06769ac0aa611ce3116faa14a3736cddc4cdbcae5f7084a0cd44c4a44a8c78a4200f34b097c0176c8c686b352c79105ffcfd764473f9d3206"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3f315ca76675299894f1ad111dcce7450e1e2f06400e64a86db5d42c67a14fc7a8f92b1063ae547229544d046bcbb038027e0e0abab05bfb6600f7349a9bf90a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.root.json
new file mode 100644
index 0000000..ecf8e78
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "6fcc711c61ce5cebe08cc4f3ed4ccfbee0d33a223cea1258d8a10af6be18627129d2ab2b063283eadcd36e67dd8713af00390aa44733ce9889e2aee2e2e1c600"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..decb1f6
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "9b7a3b750e08dfc308b46c3671df8385f782507498c03d2f9f7ecd08045e1112ae034e176f5afea79008aa6bc6dd514fc86aaeec598521d2b5e3247d28e56f0b"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "65b00baea2495c7c2093e65b249597160020ec400ec993979ac07acfc99aab4ea8a2a5ed30bde8ac6c9e7d762d2a6f508ba2aefe47b3615d7eb3d3807d6ebe08"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "02926e5f258062cad02f5246701737146bd84ce82a3641d172e0534204604be437aea4842a55964a368416679255308c0db6f7980c885d9afaeae92fc779a40d"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9efc64982359e93211f05350e1399c1953579807ace961423cdc5c2406df96180b56f94ddac14511bc8430625f230235f51a98e9c546c262f9b444e08c31c50d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "3105c9e05fb0a536586a9f241c40ffd0696bb11be43bc50ad18fb63799764139a839bee36c58e509d725b4350658c3d3c2a3e60050de66eda65a87be36b6cc03"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "7b4fd39d2babaead15732edf5cadd72b77e3e3cc8b46a5462fe9eadf4ef79cffe15d7b95d56b94d2e4776a2892c5c9ca7895ec6fabaaa563071be633375b3c03"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "24b9b1f3730d6cc8293d83ebae86e3ea3a9bbb4d0a8907b2c71001cc1b0d257814f037f5aa06f8425e871c66f7c377ab197bfceb4402144924dbbe0422103607"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "5bc6f5738109fe3be3e02c8558a241d61d7494fd76a0274774485ff6b010f41f718b184a3a743a2ceb16324e5bcd57daa6fb87cf32c85bf6c01d8a3098238308"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3e3a376641fce12b3df639293cf52c29fbf6a04398ab6e69d2bf12cfae264ace4c984816440536e870cc50816fb2b94e9b9824ce873c7237561afa9bbb649304"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/2.root.json
new file mode 100644
index 0000000..fe8ada3
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/2.root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e74e989e4ef342e273a7838703998da620fe1585a53bc9b0700c458acd83d82a8e35d69c9ef448b0ab591da6dbfc22341c950dd32ffbb1f525923c34b9047704"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/2.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/2.snapshot.json
new file mode 100644
index 0000000..7cb7305
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/2.snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "25cd6eee4f580fed660330de02cb9ab34b9e7495d3f4c57babf225c3c2c39a0937afe633a7760c19681c6b05d89f2c3907a7eef32822992624223cfbcb872d00"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2011938c47cf0990172acc5a418b9758d124655200b6874fb6646bd6025de6c5a48a3c30ced83b1f955b242c1c3f4d2d346b71861fccdc45c945418aa2511d0e"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "303b7b1062a09644bd18804b6b1af6818c25abd212b6539cab0fab0e729a4eb14621396fde4d809649c37b82db5278e878b21b52052ac4fecbcff471c8f64408"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "85961c73d21ab17e5892681198479700a995920cd02001818979fe35122e5776fcc3fd499811b1f995f922ad737d61c5b72229ab1e4d7aab63d9ea1642795c0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "4aed1e9ef8d1ac785d15df386470df92ff484f71c35a37ecd1e83e0fd66e67206172f6a2f277bdebae17372e75d253b45143adf3f9d8b6d23a4690d482e2180b"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "26ed17502d130ab4c7b26a1ef78730d9f15847cc3aa6623f692003e588554761b6f6c3b60da304933b3e012478466ce634970562504fdab9b6d2c8e818f3220e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "0b498cf7cd3a926007f158d9b72002283d67f4f0c2e4a61d5bb3d3afe09d0e9dcd41a4bc67d5cc87a2d072028904d2e022ca4d2abc84137433cdd6812f613505"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1f9aea75e9b632f06769ac0aa611ce3116faa14a3736cddc4cdbcae5f7084a0cd44c4a44a8c78a4200f34b097c0176c8c686b352c79105ffcfd764473f9d3206"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3f315ca76675299894f1ad111dcce7450e1e2f06400e64a86db5d42c67a14fc7a8f92b1063ae547229544d046bcbb038027e0e0abab05bfb6600f7349a9bf90a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/root.json
new file mode 100644
index 0000000..fe8ada3
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e74e989e4ef342e273a7838703998da620fe1585a53bc9b0700c458acd83d82a8e35d69c9ef448b0ab591da6dbfc22341c950dd32ffbb1f525923c34b9047704"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:19Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/snapshot.json
new file mode 100644
index 0000000..7cb7305
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/snapshot.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "25cd6eee4f580fed660330de02cb9ab34b9e7495d3f4c57babf225c3c2c39a0937afe633a7760c19681c6b05d89f2c3907a7eef32822992624223cfbcb872d00"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "2011938c47cf0990172acc5a418b9758d124655200b6874fb6646bd6025de6c5a48a3c30ced83b1f955b242c1c3f4d2d346b71861fccdc45c945418aa2511d0e"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "303b7b1062a09644bd18804b6b1af6818c25abd212b6539cab0fab0e729a4eb14621396fde4d809649c37b82db5278e878b21b52052ac4fecbcff471c8f64408"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "85961c73d21ab17e5892681198479700a995920cd02001818979fe35122e5776fcc3fd499811b1f995f922ad737d61c5b72229ab1e4d7aab63d9ea1642795c0d"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "4aed1e9ef8d1ac785d15df386470df92ff484f71c35a37ecd1e83e0fd66e67206172f6a2f277bdebae17372e75d253b45143adf3f9d8b6d23a4690d482e2180b"
+ },
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "26ed17502d130ab4c7b26a1ef78730d9f15847cc3aa6623f692003e588554761b6f6c3b60da304933b3e012478466ce634970562504fdab9b6d2c8e818f3220e"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "0b498cf7cd3a926007f158d9b72002283d67f4f0c2e4a61d5bb3d3afe09d0e9dcd41a4bc67d5cc87a2d072028904d2e022ca4d2abc84137433cdd6812f613505"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "1f9aea75e9b632f06769ac0aa611ce3116faa14a3736cddc4cdbcae5f7084a0cd44c4a44a8c78a4200f34b097c0176c8c686b352c79105ffcfd764473f9d3206"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "3f315ca76675299894f1ad111dcce7450e1e2f06400e64a86db5d42c67a14fc7a8f92b1063ae547229544d046bcbb038027e0e0abab05bfb6600f7349a9bf90a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/targets.json
new file mode 100644
index 0000000..56b7bfa
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "51fff9a858517278b811fc9b8dc4fee5e10c5f444a87bc259cb0241ea54f1da693cf844412693d6ea24bed5302a508d08879e0a32a961f1132a646ef9f370a07"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:19Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/timestamp.json
new file mode 100644
index 0000000..fa706c2
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_snapshot/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "de078d3c5438334ab51ed4e433cab94b53f883dad9519505ebe64088f13077f725d96b54c10afbf7909c95ddfd829d0bcca82e09612b91d63dbc9a34e830270c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:19Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "16459413316a5b9a617e84c06840164beaa4d7ef0b67c0c720acefc89cb15be3",
+ "sha512": "c118a299f2be396ef21097a0803166b1d796256a3c68e3590e1addd656e804dd28d32e67664e460066af4b956f50fcd1abd8b614e2535fb7e175d8e2819d1d87"
+ },
+ "length": 2271,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.root.json
new file mode 100644
index 0000000..c067b5f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ba2efed303d416f5e38018fb72802fb9263c87e6ad63725fd5e839d3cec51e2cfd50bdd0004a96a4017ef929249fc263332a54d0c86552f9014e68f6972b3c08"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..6a26078
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "8d1132037c4a0fb48600ecda532f1518a24e85aba93147021f763762793ff82e75fa5b7904d223085ceabbfe10206f667cd4951fab481b3712ab01115e85060a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..0dab21b
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "2a088977e3828da40fbe4f245d9d0ab6cf6e81eb30835531a1a732e75dc734214d70061ef6e1c30a1984b9d36401d257952471a974314c148823d207781fdd09"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f48198da5d992e46d55f092689c442bf31d83b9c9187d83defee46f7fea5a802c0badc1ab3ae2799492946946b29b90b2c10eaca7f5e8e33a00c555e0d5abc0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "37fffad3ba0cb53593b1d2cf7aa0437cfb821a66d00c1b3ad6dd858117b66336ad42ac7a53de241dcacbd5961e2b22d4c5c1e6535cd0595645c813a0fc222a00"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "42964abf028dd306e5a98920e90c5234dd01492259fbbfe6cafcb3d6076d34bfc296b2e57834a2f7039fdab89fee185dd2291abfec61a9bce256556a922ae006"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "6ba3c3e8485f4298bff2547bc39237d0ba07d275028ef8a7b0d051a1a8867f7d38cf5973b4f5b4acd868820dfc5d4eb86646c87fe9b3cbe77e50a2dcaf963b04"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "3e5189ac4c3c485c833ff697056e07eeae6cabc2de11a32c2a6a074d622ff22295d0ed8f3e42882caa4f59c09ceb3869a6d7b663439327e6f0e56c713ff1780e"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5688ea77f9e7020c2664920af7d48814231dea91a1997d7adec74f9e167e55dea7797603ebea9f9352c4e91306f92cb81980b9351787d90cbf23e706a26730e"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "17adcf644ace2f7e66abf30980426db96c2b1bf275ea728593433feeb90e39e4b2db1bbd5128f8fe56a1fa3db8afafe5fec2b40903b402015a9554c24d543704"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "bed1c8e2e94072dc3c8bf0f823ada338ce7eae194d3d1df6596fb49efdf59733137d02f10b8521db6b00c696d45a1f4689ae350ee1a6cbd5dcc63700da70b401"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..04f3fdf
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "09068b028ead5c22678544002721e77c9cea836ed1034ea90c5757ac8fdd121a5bb9b54891306643e45816f63edb563a2faf88c0b92f4a6da7c9102fad1cde09"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "e4f270f0f80cd430d6b7596625a9f75664ca0d0fe27f42a9bfde9041d498b737",
+ "sha512": "abeb893c9e16771a1336374ff257dcb6752c2ed3ad51de8f2a9799f2dd515fc3677bb8bb4f0420a2e423f2c87f26a2a04df9f380ea2d2d6dc6b7f9cbb337cd8f"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/root.json
new file mode 100644
index 0000000..c067b5f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ba2efed303d416f5e38018fb72802fb9263c87e6ad63725fd5e839d3cec51e2cfd50bdd0004a96a4017ef929249fc263332a54d0c86552f9014e68f6972b3c08"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..6a26078
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "8d1132037c4a0fb48600ecda532f1518a24e85aba93147021f763762793ff82e75fa5b7904d223085ceabbfe10206f667cd4951fab481b3712ab01115e85060a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/targets.json
new file mode 100644
index 0000000..0dab21b
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "2a088977e3828da40fbe4f245d9d0ab6cf6e81eb30835531a1a732e75dc734214d70061ef6e1c30a1984b9d36401d257952471a974314c148823d207781fdd09"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f48198da5d992e46d55f092689c442bf31d83b9c9187d83defee46f7fea5a802c0badc1ab3ae2799492946946b29b90b2c10eaca7f5e8e33a00c555e0d5abc0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "37fffad3ba0cb53593b1d2cf7aa0437cfb821a66d00c1b3ad6dd858117b66336ad42ac7a53de241dcacbd5961e2b22d4c5c1e6535cd0595645c813a0fc222a00"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "42964abf028dd306e5a98920e90c5234dd01492259fbbfe6cafcb3d6076d34bfc296b2e57834a2f7039fdab89fee185dd2291abfec61a9bce256556a922ae006"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "6ba3c3e8485f4298bff2547bc39237d0ba07d275028ef8a7b0d051a1a8867f7d38cf5973b4f5b4acd868820dfc5d4eb86646c87fe9b3cbe77e50a2dcaf963b04"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "3e5189ac4c3c485c833ff697056e07eeae6cabc2de11a32c2a6a074d622ff22295d0ed8f3e42882caa4f59c09ceb3869a6d7b663439327e6f0e56c713ff1780e"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5688ea77f9e7020c2664920af7d48814231dea91a1997d7adec74f9e167e55dea7797603ebea9f9352c4e91306f92cb81980b9351787d90cbf23e706a26730e"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "17adcf644ace2f7e66abf30980426db96c2b1bf275ea728593433feeb90e39e4b2db1bbd5128f8fe56a1fa3db8afafe5fec2b40903b402015a9554c24d543704"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "bed1c8e2e94072dc3c8bf0f823ada338ce7eae194d3d1df6596fb49efdf59733137d02f10b8521db6b00c696d45a1f4689ae350ee1a6cbd5dcc63700da70b401"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..04f3fdf
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "09068b028ead5c22678544002721e77c9cea836ed1034ea90c5757ac8fdd121a5bb9b54891306643e45816f63edb563a2faf88c0b92f4a6da7c9102fad1cde09"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "e4f270f0f80cd430d6b7596625a9f75664ca0d0fe27f42a9bfde9041d498b737",
+ "sha512": "abeb893c9e16771a1336374ff257dcb6752c2ed3ad51de8f2a9799f2dd515fc3677bb8bb4f0420a2e423f2c87f26a2a04df9f380ea2d2d6dc6b7f9cbb337cd8f"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..c067b5f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ba2efed303d416f5e38018fb72802fb9263c87e6ad63725fd5e839d3cec51e2cfd50bdd0004a96a4017ef929249fc263332a54d0c86552f9014e68f6972b3c08"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..6a26078
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "8d1132037c4a0fb48600ecda532f1518a24e85aba93147021f763762793ff82e75fa5b7904d223085ceabbfe10206f667cd4951fab481b3712ab01115e85060a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..0dab21b
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "2a088977e3828da40fbe4f245d9d0ab6cf6e81eb30835531a1a732e75dc734214d70061ef6e1c30a1984b9d36401d257952471a974314c148823d207781fdd09"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f48198da5d992e46d55f092689c442bf31d83b9c9187d83defee46f7fea5a802c0badc1ab3ae2799492946946b29b90b2c10eaca7f5e8e33a00c555e0d5abc0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "37fffad3ba0cb53593b1d2cf7aa0437cfb821a66d00c1b3ad6dd858117b66336ad42ac7a53de241dcacbd5961e2b22d4c5c1e6535cd0595645c813a0fc222a00"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "42964abf028dd306e5a98920e90c5234dd01492259fbbfe6cafcb3d6076d34bfc296b2e57834a2f7039fdab89fee185dd2291abfec61a9bce256556a922ae006"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "6ba3c3e8485f4298bff2547bc39237d0ba07d275028ef8a7b0d051a1a8867f7d38cf5973b4f5b4acd868820dfc5d4eb86646c87fe9b3cbe77e50a2dcaf963b04"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "3e5189ac4c3c485c833ff697056e07eeae6cabc2de11a32c2a6a074d622ff22295d0ed8f3e42882caa4f59c09ceb3869a6d7b663439327e6f0e56c713ff1780e"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5688ea77f9e7020c2664920af7d48814231dea91a1997d7adec74f9e167e55dea7797603ebea9f9352c4e91306f92cb81980b9351787d90cbf23e706a26730e"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "17adcf644ace2f7e66abf30980426db96c2b1bf275ea728593433feeb90e39e4b2db1bbd5128f8fe56a1fa3db8afafe5fec2b40903b402015a9554c24d543704"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "bed1c8e2e94072dc3c8bf0f823ada338ce7eae194d3d1df6596fb49efdf59733137d02f10b8521db6b00c696d45a1f4689ae350ee1a6cbd5dcc63700da70b401"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..04f3fdf
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "09068b028ead5c22678544002721e77c9cea836ed1034ea90c5757ac8fdd121a5bb9b54891306643e45816f63edb563a2faf88c0b92f4a6da7c9102fad1cde09"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "e4f270f0f80cd430d6b7596625a9f75664ca0d0fe27f42a9bfde9041d498b737",
+ "sha512": "abeb893c9e16771a1336374ff257dcb6752c2ed3ad51de8f2a9799f2dd515fc3677bb8bb4f0420a2e423f2c87f26a2a04df9f380ea2d2d6dc6b7f9cbb337cd8f"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/root.json
new file mode 100644
index 0000000..c067b5f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ba2efed303d416f5e38018fb72802fb9263c87e6ad63725fd5e839d3cec51e2cfd50bdd0004a96a4017ef929249fc263332a54d0c86552f9014e68f6972b3c08"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..6a26078
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "8d1132037c4a0fb48600ecda532f1518a24e85aba93147021f763762793ff82e75fa5b7904d223085ceabbfe10206f667cd4951fab481b3712ab01115e85060a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/targets.json
new file mode 100644
index 0000000..0dab21b
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "2a088977e3828da40fbe4f245d9d0ab6cf6e81eb30835531a1a732e75dc734214d70061ef6e1c30a1984b9d36401d257952471a974314c148823d207781fdd09"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f48198da5d992e46d55f092689c442bf31d83b9c9187d83defee46f7fea5a802c0badc1ab3ae2799492946946b29b90b2c10eaca7f5e8e33a00c555e0d5abc0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "37fffad3ba0cb53593b1d2cf7aa0437cfb821a66d00c1b3ad6dd858117b66336ad42ac7a53de241dcacbd5961e2b22d4c5c1e6535cd0595645c813a0fc222a00"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "42964abf028dd306e5a98920e90c5234dd01492259fbbfe6cafcb3d6076d34bfc296b2e57834a2f7039fdab89fee185dd2291abfec61a9bce256556a922ae006"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "6ba3c3e8485f4298bff2547bc39237d0ba07d275028ef8a7b0d051a1a8867f7d38cf5973b4f5b4acd868820dfc5d4eb86646c87fe9b3cbe77e50a2dcaf963b04"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "3e5189ac4c3c485c833ff697056e07eeae6cabc2de11a32c2a6a074d622ff22295d0ed8f3e42882caa4f59c09ceb3869a6d7b663439327e6f0e56c713ff1780e"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5688ea77f9e7020c2664920af7d48814231dea91a1997d7adec74f9e167e55dea7797603ebea9f9352c4e91306f92cb81980b9351787d90cbf23e706a26730e"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "17adcf644ace2f7e66abf30980426db96c2b1bf275ea728593433feeb90e39e4b2db1bbd5128f8fe56a1fa3db8afafe5fec2b40903b402015a9554c24d543704"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "bed1c8e2e94072dc3c8bf0f823ada338ce7eae194d3d1df6596fb49efdf59733137d02f10b8521db6b00c696d45a1f4689ae350ee1a6cbd5dcc63700da70b401"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..04f3fdf
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "09068b028ead5c22678544002721e77c9cea836ed1034ea90c5757ac8fdd121a5bb9b54891306643e45816f63edb563a2faf88c0b92f4a6da7c9102fad1cde09"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "e4f270f0f80cd430d6b7596625a9f75664ca0d0fe27f42a9bfde9041d498b737",
+ "sha512": "abeb893c9e16771a1336374ff257dcb6752c2ed3ad51de8f2a9799f2dd515fc3677bb8bb4f0420a2e423f2c87f26a2a04df9f380ea2d2d6dc6b7f9cbb337cd8f"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/hash.txt b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/hash.txt
new file mode 100644
index 0000000..c0a124d
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/hash.txt
@@ -0,0 +1 @@
+0d1fafc3b1e2fba17b6702b14477333fd5ed2379cfc454304a93524503f2594e \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..c067b5f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ba2efed303d416f5e38018fb72802fb9263c87e6ad63725fd5e839d3cec51e2cfd50bdd0004a96a4017ef929249fc263332a54d0c86552f9014e68f6972b3c08"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..6a26078
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "8d1132037c4a0fb48600ecda532f1518a24e85aba93147021f763762793ff82e75fa5b7904d223085ceabbfe10206f667cd4951fab481b3712ab01115e85060a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..0dab21b
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "2a088977e3828da40fbe4f245d9d0ab6cf6e81eb30835531a1a732e75dc734214d70061ef6e1c30a1984b9d36401d257952471a974314c148823d207781fdd09"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f48198da5d992e46d55f092689c442bf31d83b9c9187d83defee46f7fea5a802c0badc1ab3ae2799492946946b29b90b2c10eaca7f5e8e33a00c555e0d5abc0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "37fffad3ba0cb53593b1d2cf7aa0437cfb821a66d00c1b3ad6dd858117b66336ad42ac7a53de241dcacbd5961e2b22d4c5c1e6535cd0595645c813a0fc222a00"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "42964abf028dd306e5a98920e90c5234dd01492259fbbfe6cafcb3d6076d34bfc296b2e57834a2f7039fdab89fee185dd2291abfec61a9bce256556a922ae006"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "6ba3c3e8485f4298bff2547bc39237d0ba07d275028ef8a7b0d051a1a8867f7d38cf5973b4f5b4acd868820dfc5d4eb86646c87fe9b3cbe77e50a2dcaf963b04"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "3e5189ac4c3c485c833ff697056e07eeae6cabc2de11a32c2a6a074d622ff22295d0ed8f3e42882caa4f59c09ceb3869a6d7b663439327e6f0e56c713ff1780e"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5688ea77f9e7020c2664920af7d48814231dea91a1997d7adec74f9e167e55dea7797603ebea9f9352c4e91306f92cb81980b9351787d90cbf23e706a26730e"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "17adcf644ace2f7e66abf30980426db96c2b1bf275ea728593433feeb90e39e4b2db1bbd5128f8fe56a1fa3db8afafe5fec2b40903b402015a9554c24d543704"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "bed1c8e2e94072dc3c8bf0f823ada338ce7eae194d3d1df6596fb49efdf59733137d02f10b8521db6b00c696d45a1f4689ae350ee1a6cbd5dcc63700da70b401"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..04f3fdf
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "09068b028ead5c22678544002721e77c9cea836ed1034ea90c5757ac8fdd121a5bb9b54891306643e45816f63edb563a2faf88c0b92f4a6da7c9102fad1cde09"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "e4f270f0f80cd430d6b7596625a9f75664ca0d0fe27f42a9bfde9041d498b737",
+ "sha512": "abeb893c9e16771a1336374ff257dcb6752c2ed3ad51de8f2a9799f2dd515fc3677bb8bb4f0420a2e423f2c87f26a2a04df9f380ea2d2d6dc6b7f9cbb337cd8f"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..216adcd
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/2.root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "5221883d99795f64519d986c33744cf101b50d098735859192a6536a62f2af62d28c7d3348f407fd4302b6c6be8198b5fb33b41a660bc998751626898e954105"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/2.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/2.targets.json
new file mode 100644
index 0000000..e3be9d9
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/2.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "24efa3e5c6463307219075f5c4e816a3c2643024d36909735dbcd617c4300246b4384db27cefce01a8481b6c52f5dad2f3f416830912bf0b0d8398c14508e705"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f9304ff820dbb0f245baa68291ffaa901fa160d446a2fbe864e561afc74006c1f47e112eef2f51a78efa87335d2dc86392d0d3c84d69744366202caa35c80801"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "d2e5e871f943281e59bce283ac874bdd9ec1ed461bf9a7dce48774b577fd481fe3e0975670fbc684d0bbc881974e832cef02ced09f0e533a621266b81b8f1307"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "d3044468af5877042ef1585a83d8dc59bf4bd8f0b20837d6e73d39828ed15c1e13fb64ab02d9dbc2940768b02ac95d8cc17d05d9c9fea4200a98453b8463c408"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "cb5da30b57e62e562cb8897adde8ecfc195d9d513ec59ed4931c5d8f1612518f644e7e78f674b8de646373747bcf08a4025351167c3a77d8eff382823d18670b"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "eccd5243a12063c73c56ecad469da0c2ce96dbfd4f04c8f6c5b1456336193d92f6ef69efb5ec6e69d29e868983a576d2baab66e983b7ae7bee553f5fcd9ef909"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "4892c826626dd62c189707efcd1b340ed6bf6e68833eefb1f94020bf59a9c4eefafacfa4a3998d149fca8ad5a30e12f8aa1a936821c45726d445403df0688508"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "12b6240c2d9a922a35591e3d86877f4a407210f7e6d3bf270ef2e496394029f731f77cb3220a361b0b49b54f5564969aaa62262219db0a5b58b86fd0cf42420e"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "894a62b4b9ae92e45298abd9465aeedeba40eb82a4ca014d41f1b58b6117d9ab3e89c2d152e2a7adce99396bc53c9f1324dbb4af8ff00e251f3f16e1ed52f103"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/root.json
new file mode 100644
index 0000000..216adcd
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "5221883d99795f64519d986c33744cf101b50d098735859192a6536a62f2af62d28c7d3348f407fd4302b6c6be8198b5fb33b41a660bc998751626898e954105"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..6a26078
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "8d1132037c4a0fb48600ecda532f1518a24e85aba93147021f763762793ff82e75fa5b7904d223085ceabbfe10206f667cd4951fab481b3712ab01115e85060a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/targets.json
new file mode 100644
index 0000000..e3be9d9
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "24efa3e5c6463307219075f5c4e816a3c2643024d36909735dbcd617c4300246b4384db27cefce01a8481b6c52f5dad2f3f416830912bf0b0d8398c14508e705"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f9304ff820dbb0f245baa68291ffaa901fa160d446a2fbe864e561afc74006c1f47e112eef2f51a78efa87335d2dc86392d0d3c84d69744366202caa35c80801"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "d2e5e871f943281e59bce283ac874bdd9ec1ed461bf9a7dce48774b577fd481fe3e0975670fbc684d0bbc881974e832cef02ced09f0e533a621266b81b8f1307"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "d3044468af5877042ef1585a83d8dc59bf4bd8f0b20837d6e73d39828ed15c1e13fb64ab02d9dbc2940768b02ac95d8cc17d05d9c9fea4200a98453b8463c408"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "cb5da30b57e62e562cb8897adde8ecfc195d9d513ec59ed4931c5d8f1612518f644e7e78f674b8de646373747bcf08a4025351167c3a77d8eff382823d18670b"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "eccd5243a12063c73c56ecad469da0c2ce96dbfd4f04c8f6c5b1456336193d92f6ef69efb5ec6e69d29e868983a576d2baab66e983b7ae7bee553f5fcd9ef909"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "4892c826626dd62c189707efcd1b340ed6bf6e68833eefb1f94020bf59a9c4eefafacfa4a3998d149fca8ad5a30e12f8aa1a936821c45726d445403df0688508"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "12b6240c2d9a922a35591e3d86877f4a407210f7e6d3bf270ef2e496394029f731f77cb3220a361b0b49b54f5564969aaa62262219db0a5b58b86fd0cf42420e"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "894a62b4b9ae92e45298abd9465aeedeba40eb82a4ca014d41f1b58b6117d9ab3e89c2d152e2a7adce99396bc53c9f1324dbb4af8ff00e251f3f16e1ed52f103"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..04f3fdf
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata.staged/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "09068b028ead5c22678544002721e77c9cea836ed1034ea90c5757ac8fdd121a5bb9b54891306643e45816f63edb563a2faf88c0b92f4a6da7c9102fad1cde09"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "e4f270f0f80cd430d6b7596625a9f75664ca0d0fe27f42a9bfde9041d498b737",
+ "sha512": "abeb893c9e16771a1336374ff257dcb6752c2ed3ad51de8f2a9799f2dd515fc3677bb8bb4f0420a2e423f2c87f26a2a04df9f380ea2d2d6dc6b7f9cbb337cd8f"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.root.json
new file mode 100644
index 0000000..c067b5f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "ba2efed303d416f5e38018fb72802fb9263c87e6ad63725fd5e839d3cec51e2cfd50bdd0004a96a4017ef929249fc263332a54d0c86552f9014e68f6972b3c08"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..6a26078
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "8d1132037c4a0fb48600ecda532f1518a24e85aba93147021f763762793ff82e75fa5b7904d223085ceabbfe10206f667cd4951fab481b3712ab01115e85060a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.targets.json
new file mode 100644
index 0000000..0dab21b
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "2a088977e3828da40fbe4f245d9d0ab6cf6e81eb30835531a1a732e75dc734214d70061ef6e1c30a1984b9d36401d257952471a974314c148823d207781fdd09"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f48198da5d992e46d55f092689c442bf31d83b9c9187d83defee46f7fea5a802c0badc1ab3ae2799492946946b29b90b2c10eaca7f5e8e33a00c555e0d5abc0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "37fffad3ba0cb53593b1d2cf7aa0437cfb821a66d00c1b3ad6dd858117b66336ad42ac7a53de241dcacbd5961e2b22d4c5c1e6535cd0595645c813a0fc222a00"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "42964abf028dd306e5a98920e90c5234dd01492259fbbfe6cafcb3d6076d34bfc296b2e57834a2f7039fdab89fee185dd2291abfec61a9bce256556a922ae006"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "6ba3c3e8485f4298bff2547bc39237d0ba07d275028ef8a7b0d051a1a8867f7d38cf5973b4f5b4acd868820dfc5d4eb86646c87fe9b3cbe77e50a2dcaf963b04"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "3e5189ac4c3c485c833ff697056e07eeae6cabc2de11a32c2a6a074d622ff22295d0ed8f3e42882caa4f59c09ceb3869a6d7b663439327e6f0e56c713ff1780e"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c5688ea77f9e7020c2664920af7d48814231dea91a1997d7adec74f9e167e55dea7797603ebea9f9352c4e91306f92cb81980b9351787d90cbf23e706a26730e"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "17adcf644ace2f7e66abf30980426db96c2b1bf275ea728593433feeb90e39e4b2db1bbd5128f8fe56a1fa3db8afafe5fec2b40903b402015a9554c24d543704"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "bed1c8e2e94072dc3c8bf0f823ada338ce7eae194d3d1df6596fb49efdf59733137d02f10b8521db6b00c696d45a1f4689ae350ee1a6cbd5dcc63700da70b401"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..04f3fdf
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "09068b028ead5c22678544002721e77c9cea836ed1034ea90c5757ac8fdd121a5bb9b54891306643e45816f63edb563a2faf88c0b92f4a6da7c9102fad1cde09"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "e4f270f0f80cd430d6b7596625a9f75664ca0d0fe27f42a9bfde9041d498b737",
+ "sha512": "abeb893c9e16771a1336374ff257dcb6752c2ed3ad51de8f2a9799f2dd515fc3677bb8bb4f0420a2e423f2c87f26a2a04df9f380ea2d2d6dc6b7f9cbb337cd8f"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/2.root.json
new file mode 100644
index 0000000..216adcd
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/2.root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "5221883d99795f64519d986c33744cf101b50d098735859192a6536a62f2af62d28c7d3348f407fd4302b6c6be8198b5fb33b41a660bc998751626898e954105"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/2.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/2.targets.json
new file mode 100644
index 0000000..e3be9d9
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/2.targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "24efa3e5c6463307219075f5c4e816a3c2643024d36909735dbcd617c4300246b4384db27cefce01a8481b6c52f5dad2f3f416830912bf0b0d8398c14508e705"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f9304ff820dbb0f245baa68291ffaa901fa160d446a2fbe864e561afc74006c1f47e112eef2f51a78efa87335d2dc86392d0d3c84d69744366202caa35c80801"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "d2e5e871f943281e59bce283ac874bdd9ec1ed461bf9a7dce48774b577fd481fe3e0975670fbc684d0bbc881974e832cef02ced09f0e533a621266b81b8f1307"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "d3044468af5877042ef1585a83d8dc59bf4bd8f0b20837d6e73d39828ed15c1e13fb64ab02d9dbc2940768b02ac95d8cc17d05d9c9fea4200a98453b8463c408"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "cb5da30b57e62e562cb8897adde8ecfc195d9d513ec59ed4931c5d8f1612518f644e7e78f674b8de646373747bcf08a4025351167c3a77d8eff382823d18670b"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "eccd5243a12063c73c56ecad469da0c2ce96dbfd4f04c8f6c5b1456336193d92f6ef69efb5ec6e69d29e868983a576d2baab66e983b7ae7bee553f5fcd9ef909"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "4892c826626dd62c189707efcd1b340ed6bf6e68833eefb1f94020bf59a9c4eefafacfa4a3998d149fca8ad5a30e12f8aa1a936821c45726d445403df0688508"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "12b6240c2d9a922a35591e3d86877f4a407210f7e6d3bf270ef2e496394029f731f77cb3220a361b0b49b54f5564969aaa62262219db0a5b58b86fd0cf42420e"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "894a62b4b9ae92e45298abd9465aeedeba40eb82a4ca014d41f1b58b6117d9ab3e89c2d152e2a7adce99396bc53c9f1324dbb4af8ff00e251f3f16e1ed52f103"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/root.json
new file mode 100644
index 0000000..216adcd
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "5221883d99795f64519d986c33744cf101b50d098735859192a6536a62f2af62d28c7d3348f407fd4302b6c6be8198b5fb33b41a660bc998751626898e954105"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:22Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/snapshot.json
new file mode 100644
index 0000000..6a26078
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "8d1132037c4a0fb48600ecda532f1518a24e85aba93147021f763762793ff82e75fa5b7904d223085ceabbfe10206f667cd4951fab481b3712ab01115e85060a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/targets.json
new file mode 100644
index 0000000..e3be9d9
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/targets.json
@@ -0,0 +1,51 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "24efa3e5c6463307219075f5c4e816a3c2643024d36909735dbcd617c4300246b4384db27cefce01a8481b6c52f5dad2f3f416830912bf0b0d8398c14508e705"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "f9304ff820dbb0f245baa68291ffaa901fa160d446a2fbe864e561afc74006c1f47e112eef2f51a78efa87335d2dc86392d0d3c84d69744366202caa35c80801"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "d2e5e871f943281e59bce283ac874bdd9ec1ed461bf9a7dce48774b577fd481fe3e0975670fbc684d0bbc881974e832cef02ced09f0e533a621266b81b8f1307"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "d3044468af5877042ef1585a83d8dc59bf4bd8f0b20837d6e73d39828ed15c1e13fb64ab02d9dbc2940768b02ac95d8cc17d05d9c9fea4200a98453b8463c408"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "cb5da30b57e62e562cb8897adde8ecfc195d9d513ec59ed4931c5d8f1612518f644e7e78f674b8de646373747bcf08a4025351167c3a77d8eff382823d18670b"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "eccd5243a12063c73c56ecad469da0c2ce96dbfd4f04c8f6c5b1456336193d92f6ef69efb5ec6e69d29e868983a576d2baab66e983b7ae7bee553f5fcd9ef909"
+ },
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "4892c826626dd62c189707efcd1b340ed6bf6e68833eefb1f94020bf59a9c4eefafacfa4a3998d149fca8ad5a30e12f8aa1a936821c45726d445403df0688508"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "12b6240c2d9a922a35591e3d86877f4a407210f7e6d3bf270ef2e496394029f731f77cb3220a361b0b49b54f5564969aaa62262219db0a5b58b86fd0cf42420e"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "894a62b4b9ae92e45298abd9465aeedeba40eb82a4ca014d41f1b58b6117d9ab3e89c2d152e2a7adce99396bc53c9f1324dbb4af8ff00e251f3f16e1ed52f103"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:22Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/timestamp.json
new file mode 100644
index 0000000..04f3fdf
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_targets/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "09068b028ead5c22678544002721e77c9cea836ed1034ea90c5757ac8fdd121a5bb9b54891306643e45816f63edb563a2faf88c0b92f4a6da7c9102fad1cde09"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:22Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "e4f270f0f80cd430d6b7596625a9f75664ca0d0fe27f42a9bfde9041d498b737",
+ "sha512": "abeb893c9e16771a1336374ff257dcb6752c2ed3ad51de8f2a9799f2dd515fc3677bb8bb4f0420a2e423f2c87f26a2a04df9f380ea2d2d6dc6b7f9cbb337cd8f"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.root.json
new file mode 100644
index 0000000..cc719f7
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "87ccdd9f898665507c07b9b5ae4b19cc5069a35db47d2bb59fe31324c019f384701a650966965f680c2fb2e43950beaac3818c430a5e0450a2d61439273b4803"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..1a026e5
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..f95627f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/1.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0d88a87a5c298a1629f784cdad65c1fc6bf65a2a5e6fe92912e86d0c86ef5a8d4a90b978b1b80c980fe5f04cc78f76b1fb388d964208c3136690b12054da4008"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "570e7580e1087363f1c1ba92b97cb92d5157a21c044ad3ae3f7638b087c8d8257cade3db5d40e0006ef41fa90c7cc6fbff8fc3ebe91324edfcd4e539617f6c00"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "4f7000d954a4133cb731c60f45bbfaf172327b866570f8f81035c8177cf0d1ec725fe4c1e48be94d649ee69a9434c0a00aff85aaf3198b242783569995d60c03"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "952a8b5311c810be01ee3b79aeb837a408e53aa316d8b3b79843e27899f951fbd6b335507739c045582a6abc47d43912ba7533786342d1a726cf96c8b2464806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "58c5523a8f0b127b320772c1525ad3d5c98ce1217a178b02c97ea855a3a1e7d3037b5f7c21a485d6bf1815c11f4e56a874a6c4c72ab26590246ba7ce51c3f001"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5250b1ef0952043c6787cb0818e0bc9ab309e57ad40763471a06e72df759b952043b33de0b5f3d5ee5c08ab9143248dc5eabb81b236548cb8241a06a0d3bd405"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "27735b3d6b7f6cb928f4b5ea689127d4f22213dc49560e3aa6043cb385a1cf2656bc806bbade810bb301c0ba906b638691ba0cf7557b5f7fb696a15c3a581005"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "5d4076732895f6ef7419cbaab36e61e18e99c19320ef9fb94b9b34a7146f1d790940847986ed61fca47bfce36ab67f19e476232fb3d30dcc690c887c2eb9d704"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/root.json
new file mode 100644
index 0000000..cc719f7
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "87ccdd9f898665507c07b9b5ae4b19cc5069a35db47d2bb59fe31324c019f384701a650966965f680c2fb2e43950beaac3818c430a5e0450a2d61439273b4803"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/targets.json
new file mode 100644
index 0000000..1a026e5
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..f95627f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/current/timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0d88a87a5c298a1629f784cdad65c1fc6bf65a2a5e6fe92912e86d0c86ef5a8d4a90b978b1b80c980fe5f04cc78f76b1fb388d964208c3136690b12054da4008"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "570e7580e1087363f1c1ba92b97cb92d5157a21c044ad3ae3f7638b087c8d8257cade3db5d40e0006ef41fa90c7cc6fbff8fc3ebe91324edfcd4e539617f6c00"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "4f7000d954a4133cb731c60f45bbfaf172327b866570f8f81035c8177cf0d1ec725fe4c1e48be94d649ee69a9434c0a00aff85aaf3198b242783569995d60c03"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "952a8b5311c810be01ee3b79aeb837a408e53aa316d8b3b79843e27899f951fbd6b335507739c045582a6abc47d43912ba7533786342d1a726cf96c8b2464806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "58c5523a8f0b127b320772c1525ad3d5c98ce1217a178b02c97ea855a3a1e7d3037b5f7c21a485d6bf1815c11f4e56a874a6c4c72ab26590246ba7ce51c3f001"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5250b1ef0952043c6787cb0818e0bc9ab309e57ad40763471a06e72df759b952043b33de0b5f3d5ee5c08ab9143248dc5eabb81b236548cb8241a06a0d3bd405"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "27735b3d6b7f6cb928f4b5ea689127d4f22213dc49560e3aa6043cb385a1cf2656bc806bbade810bb301c0ba906b638691ba0cf7557b5f7fb696a15c3a581005"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "5d4076732895f6ef7419cbaab36e61e18e99c19320ef9fb94b9b34a7146f1d790940847986ed61fca47bfce36ab67f19e476232fb3d30dcc690c887c2eb9d704"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..cc719f7
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "87ccdd9f898665507c07b9b5ae4b19cc5069a35db47d2bb59fe31324c019f384701a650966965f680c2fb2e43950beaac3818c430a5e0450a2d61439273b4803"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..1a026e5
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..f95627f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0d88a87a5c298a1629f784cdad65c1fc6bf65a2a5e6fe92912e86d0c86ef5a8d4a90b978b1b80c980fe5f04cc78f76b1fb388d964208c3136690b12054da4008"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "570e7580e1087363f1c1ba92b97cb92d5157a21c044ad3ae3f7638b087c8d8257cade3db5d40e0006ef41fa90c7cc6fbff8fc3ebe91324edfcd4e539617f6c00"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "4f7000d954a4133cb731c60f45bbfaf172327b866570f8f81035c8177cf0d1ec725fe4c1e48be94d649ee69a9434c0a00aff85aaf3198b242783569995d60c03"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "952a8b5311c810be01ee3b79aeb837a408e53aa316d8b3b79843e27899f951fbd6b335507739c045582a6abc47d43912ba7533786342d1a726cf96c8b2464806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "58c5523a8f0b127b320772c1525ad3d5c98ce1217a178b02c97ea855a3a1e7d3037b5f7c21a485d6bf1815c11f4e56a874a6c4c72ab26590246ba7ce51c3f001"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5250b1ef0952043c6787cb0818e0bc9ab309e57ad40763471a06e72df759b952043b33de0b5f3d5ee5c08ab9143248dc5eabb81b236548cb8241a06a0d3bd405"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "27735b3d6b7f6cb928f4b5ea689127d4f22213dc49560e3aa6043cb385a1cf2656bc806bbade810bb301c0ba906b638691ba0cf7557b5f7fb696a15c3a581005"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "5d4076732895f6ef7419cbaab36e61e18e99c19320ef9fb94b9b34a7146f1d790940847986ed61fca47bfce36ab67f19e476232fb3d30dcc690c887c2eb9d704"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/root.json
new file mode 100644
index 0000000..cc719f7
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "87ccdd9f898665507c07b9b5ae4b19cc5069a35db47d2bb59fe31324c019f384701a650966965f680c2fb2e43950beaac3818c430a5e0450a2d61439273b4803"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/targets.json
new file mode 100644
index 0000000..1a026e5
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..f95627f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/client/metadata/previous/timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0d88a87a5c298a1629f784cdad65c1fc6bf65a2a5e6fe92912e86d0c86ef5a8d4a90b978b1b80c980fe5f04cc78f76b1fb388d964208c3136690b12054da4008"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "570e7580e1087363f1c1ba92b97cb92d5157a21c044ad3ae3f7638b087c8d8257cade3db5d40e0006ef41fa90c7cc6fbff8fc3ebe91324edfcd4e539617f6c00"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "4f7000d954a4133cb731c60f45bbfaf172327b866570f8f81035c8177cf0d1ec725fe4c1e48be94d649ee69a9434c0a00aff85aaf3198b242783569995d60c03"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "952a8b5311c810be01ee3b79aeb837a408e53aa316d8b3b79843e27899f951fbd6b335507739c045582a6abc47d43912ba7533786342d1a726cf96c8b2464806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "58c5523a8f0b127b320772c1525ad3d5c98ce1217a178b02c97ea855a3a1e7d3037b5f7c21a485d6bf1815c11f4e56a874a6c4c72ab26590246ba7ce51c3f001"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5250b1ef0952043c6787cb0818e0bc9ab309e57ad40763471a06e72df759b952043b33de0b5f3d5ee5c08ab9143248dc5eabb81b236548cb8241a06a0d3bd405"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "27735b3d6b7f6cb928f4b5ea689127d4f22213dc49560e3aa6043cb385a1cf2656bc806bbade810bb301c0ba906b638691ba0cf7557b5f7fb696a15c3a581005"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "5d4076732895f6ef7419cbaab36e61e18e99c19320ef9fb94b9b34a7146f1d790940847986ed61fca47bfce36ab67f19e476232fb3d30dcc690c887c2eb9d704"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/hash.txt b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/hash.txt
new file mode 100644
index 0000000..9f6c833
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/hash.txt
@@ -0,0 +1 @@
+2d2c91c4805412779df23c78ae6477ea01ca2bccc740eb1750b6b8d4e02d527a \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.root.json
new file mode 100644
index 0000000..cc719f7
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "87ccdd9f898665507c07b9b5ae4b19cc5069a35db47d2bb59fe31324c019f384701a650966965f680c2fb2e43950beaac3818c430a5e0450a2d61439273b4803"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.targets.json
new file mode 100644
index 0000000..1a026e5
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.timestamp.json
new file mode 100644
index 0000000..f95627f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/1.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0d88a87a5c298a1629f784cdad65c1fc6bf65a2a5e6fe92912e86d0c86ef5a8d4a90b978b1b80c980fe5f04cc78f76b1fb388d964208c3136690b12054da4008"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "570e7580e1087363f1c1ba92b97cb92d5157a21c044ad3ae3f7638b087c8d8257cade3db5d40e0006ef41fa90c7cc6fbff8fc3ebe91324edfcd4e539617f6c00"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "4f7000d954a4133cb731c60f45bbfaf172327b866570f8f81035c8177cf0d1ec725fe4c1e48be94d649ee69a9434c0a00aff85aaf3198b242783569995d60c03"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "952a8b5311c810be01ee3b79aeb837a408e53aa316d8b3b79843e27899f951fbd6b335507739c045582a6abc47d43912ba7533786342d1a726cf96c8b2464806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "58c5523a8f0b127b320772c1525ad3d5c98ce1217a178b02c97ea855a3a1e7d3037b5f7c21a485d6bf1815c11f4e56a874a6c4c72ab26590246ba7ce51c3f001"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5250b1ef0952043c6787cb0818e0bc9ab309e57ad40763471a06e72df759b952043b33de0b5f3d5ee5c08ab9143248dc5eabb81b236548cb8241a06a0d3bd405"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "27735b3d6b7f6cb928f4b5ea689127d4f22213dc49560e3aa6043cb385a1cf2656bc806bbade810bb301c0ba906b638691ba0cf7557b5f7fb696a15c3a581005"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "5d4076732895f6ef7419cbaab36e61e18e99c19320ef9fb94b9b34a7146f1d790940847986ed61fca47bfce36ab67f19e476232fb3d30dcc690c887c2eb9d704"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/2.root.json
new file mode 100644
index 0000000..5ecd343
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/2.root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "af983d51f4deacf728c3076b7a5c79dd8c8d2c1c25071ec1523ca4d18f47e1d5e39a411ae18f437267079300fe0c74331e0f3231017f9a39a2f11628079e3406"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/2.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/2.timestamp.json
new file mode 100644
index 0000000..be3cf94
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/2.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "ec1d63e79fadec3dddb5852a1007fa7e5ed4b746b3d608ba9d3fa6fa2f25afa308a72474d820fe2b88adf4f9d6352e57fe7475725e539858f1ab07ab7a5ca305"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "c3021d40208c80b25034d7d7d29d2faeeb7b0e4e3238430a0f2c0435337d3b2c47e87f4ec2c5692dc2aec01dee8e72feea2b6f72cb17591aca1e5252fe2c4f0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "dbe051e077a6d7ad54ad39c41e3fc497c65b8f5cd9ac9b9c7bf0900dba979614198922232fcd2ef66ec9d5e571b9e936cd997c41e74c55abce8448d13b178007"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "76946f3caeba163ff8fcaa9892f92457a9531da85092ddff660d8af2859fc94ea714e1962ab704a4e028fe48b195b850d6c29d13cf668bb2bf41b0814229b402"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9f5c7234e8c67e15fa326bc693595b6ac75aacbec4c5c6bd07bb4c8d8e9bfb35d1ee121987629e45fbb2f4fdcd6593a1f4cd1231b361b89832dcc19be248f70a"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "d33845b0bfc6ace034377acc4e043dedd3c26c1d58b2f11bb3fa9416c86f55a396c3547d5f95662cbafe231af913752be851947413fca7fa5ec86948aae8ae0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "f94b9c239f481f74715627accec1f2794faeefbc0a110e4ac4c8ab0f3451812042ea5b5f85b66b568ea2b00ac46b3d5496b184bc6a59fdee1361b6dbf5e6d90d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "b17e9fed31051906f6d31c81db44c025718ced629d2d2914e09c0da4f303163bbfbd11673491aafcce6e35226b3c2f1a6fefa3ff5f39b838d4c3d736e521ac07"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "b82b9a726284bbc488f3fa8e7b91cd641b63aa0488c7706d7a8a09eecd9a12ce99e02fe77e0994084e72316476e26df42aac2d89616674e40c82a1c3f7082409"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/root.json
new file mode 100644
index 0000000..5ecd343
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "af983d51f4deacf728c3076b7a5c79dd8c8d2c1c25071ec1523ca4d18f47e1d5e39a411ae18f437267079300fe0c74331e0f3231017f9a39a2f11628079e3406"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/targets.json
new file mode 100644
index 0000000..1a026e5
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/timestamp.json
new file mode 100644
index 0000000..be3cf94
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata.staged/timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "ec1d63e79fadec3dddb5852a1007fa7e5ed4b746b3d608ba9d3fa6fa2f25afa308a72474d820fe2b88adf4f9d6352e57fe7475725e539858f1ab07ab7a5ca305"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "c3021d40208c80b25034d7d7d29d2faeeb7b0e4e3238430a0f2c0435337d3b2c47e87f4ec2c5692dc2aec01dee8e72feea2b6f72cb17591aca1e5252fe2c4f0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "dbe051e077a6d7ad54ad39c41e3fc497c65b8f5cd9ac9b9c7bf0900dba979614198922232fcd2ef66ec9d5e571b9e936cd997c41e74c55abce8448d13b178007"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "76946f3caeba163ff8fcaa9892f92457a9531da85092ddff660d8af2859fc94ea714e1962ab704a4e028fe48b195b850d6c29d13cf668bb2bf41b0814229b402"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9f5c7234e8c67e15fa326bc693595b6ac75aacbec4c5c6bd07bb4c8d8e9bfb35d1ee121987629e45fbb2f4fdcd6593a1f4cd1231b361b89832dcc19be248f70a"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "d33845b0bfc6ace034377acc4e043dedd3c26c1d58b2f11bb3fa9416c86f55a396c3547d5f95662cbafe231af913752be851947413fca7fa5ec86948aae8ae0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "f94b9c239f481f74715627accec1f2794faeefbc0a110e4ac4c8ab0f3451812042ea5b5f85b66b568ea2b00ac46b3d5496b184bc6a59fdee1361b6dbf5e6d90d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "b17e9fed31051906f6d31c81db44c025718ced629d2d2914e09c0da4f303163bbfbd11673491aafcce6e35226b3c2f1a6fefa3ff5f39b838d4c3d736e521ac07"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "b82b9a726284bbc488f3fa8e7b91cd641b63aa0488c7706d7a8a09eecd9a12ce99e02fe77e0994084e72316476e26df42aac2d89616674e40c82a1c3f7082409"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.root.json
new file mode 100644
index 0000000..cc719f7
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.root.json
@@ -0,0 +1,183 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "87ccdd9f898665507c07b9b5ae4b19cc5069a35db47d2bb59fe31324c019f384701a650966965f680c2fb2e43950beaac3818c430a5e0450a2d61439273b4803"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.targets.json
new file mode 100644
index 0000000..1a026e5
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..f95627f
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/1.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "0d88a87a5c298a1629f784cdad65c1fc6bf65a2a5e6fe92912e86d0c86ef5a8d4a90b978b1b80c980fe5f04cc78f76b1fb388d964208c3136690b12054da4008"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "570e7580e1087363f1c1ba92b97cb92d5157a21c044ad3ae3f7638b087c8d8257cade3db5d40e0006ef41fa90c7cc6fbff8fc3ebe91324edfcd4e539617f6c00"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "4f7000d954a4133cb731c60f45bbfaf172327b866570f8f81035c8177cf0d1ec725fe4c1e48be94d649ee69a9434c0a00aff85aaf3198b242783569995d60c03"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "af71d15c2434dbd9ed4e2eb05628227e9ce9ced8eb9546153544e928f35f18e9093c5ba3a5ee30d71d2000065e0a94a729e5db90e519839b3255c627922d5706"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "952a8b5311c810be01ee3b79aeb837a408e53aa316d8b3b79843e27899f951fbd6b335507739c045582a6abc47d43912ba7533786342d1a726cf96c8b2464806"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "58c5523a8f0b127b320772c1525ad3d5c98ce1217a178b02c97ea855a3a1e7d3037b5f7c21a485d6bf1815c11f4e56a874a6c4c72ab26590246ba7ce51c3f001"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "5250b1ef0952043c6787cb0818e0bc9ab309e57ad40763471a06e72df759b952043b33de0b5f3d5ee5c08ab9143248dc5eabb81b236548cb8241a06a0d3bd405"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "27735b3d6b7f6cb928f4b5ea689127d4f22213dc49560e3aa6043cb385a1cf2656bc806bbade810bb301c0ba906b638691ba0cf7557b5f7fb696a15c3a581005"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "5d4076732895f6ef7419cbaab36e61e18e99c19320ef9fb94b9b34a7146f1d790940847986ed61fca47bfce36ab67f19e476232fb3d30dcc690c887c2eb9d704"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/2.root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/2.root.json
new file mode 100644
index 0000000..5ecd343
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/2.root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "af983d51f4deacf728c3076b7a5c79dd8c8d2c1c25071ec1523ca4d18f47e1d5e39a411ae18f437267079300fe0c74331e0f3231017f9a39a2f11628079e3406"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/2.timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/2.timestamp.json
new file mode 100644
index 0000000..be3cf94
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/2.timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "ec1d63e79fadec3dddb5852a1007fa7e5ed4b746b3d608ba9d3fa6fa2f25afa308a72474d820fe2b88adf4f9d6352e57fe7475725e539858f1ab07ab7a5ca305"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "c3021d40208c80b25034d7d7d29d2faeeb7b0e4e3238430a0f2c0435337d3b2c47e87f4ec2c5692dc2aec01dee8e72feea2b6f72cb17591aca1e5252fe2c4f0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "dbe051e077a6d7ad54ad39c41e3fc497c65b8f5cd9ac9b9c7bf0900dba979614198922232fcd2ef66ec9d5e571b9e936cd997c41e74c55abce8448d13b178007"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "76946f3caeba163ff8fcaa9892f92457a9531da85092ddff660d8af2859fc94ea714e1962ab704a4e028fe48b195b850d6c29d13cf668bb2bf41b0814229b402"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9f5c7234e8c67e15fa326bc693595b6ac75aacbec4c5c6bd07bb4c8d8e9bfb35d1ee121987629e45fbb2f4fdcd6593a1f4cd1231b361b89832dcc19be248f70a"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "d33845b0bfc6ace034377acc4e043dedd3c26c1d58b2f11bb3fa9416c86f55a396c3547d5f95662cbafe231af913752be851947413fca7fa5ec86948aae8ae0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "f94b9c239f481f74715627accec1f2794faeefbc0a110e4ac4c8ab0f3451812042ea5b5f85b66b568ea2b00ac46b3d5496b184bc6a59fdee1361b6dbf5e6d90d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "b17e9fed31051906f6d31c81db44c025718ced629d2d2914e09c0da4f303163bbfbd11673491aafcce6e35226b3c2f1a6fefa3ff5f39b838d4c3d736e521ac07"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "b82b9a726284bbc488f3fa8e7b91cd641b63aa0488c7706d7a8a09eecd9a12ce99e02fe77e0994084e72316476e26df42aac2d89616674e40c82a1c3f7082409"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/root.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/root.json
new file mode 100644
index 0000000..5ecd343
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/root.json
@@ -0,0 +1,135 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "af983d51f4deacf728c3076b7a5c79dd8c8d2c1c25071ec1523ca4d18f47e1d5e39a411ae18f437267079300fe0c74331e0f3231017f9a39a2f11628079e3406"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2031-09-09T23:20:21Z",
+ "keys": {
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f6b299bcfb4e15ef652c80e9e3dda995acbb4bf71dce889a82ba70228e45a8bf"
+ },
+ "scheme": "ed25519"
+ },
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "9b1db36a5cad80284b5f40b040621e0e444f25ee09efa5c1fd6da4499c711bd5"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8"
+ ],
+ "threshold": 4
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/snapshot.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/snapshot.json
new file mode 100644
index 0000000..1e51a23
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "5939785ca69e19f3dd0e1c9a6fe36179625528fb36e30173a88dbdf139eb1ed7f9f3e0f4e0db075ff51a74141e70669cb329a042938369faa3ca1d5605c0980a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/targets.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/targets.json
new file mode 100644
index 0000000..1a026e5
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "6745efba317ff34aea61685be997d9ee18d02fee2445cfd9cef32da940d2497b12986b95edbc9be38302bbca90ade299697ad2506ce354b8a3ea9528898fab09"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2031-09-09T23:20:21Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/timestamp.json b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/timestamp.json
new file mode 100644
index 0000000..be3cf94
--- /dev/null
+++ b/client/testdata/PublishedTwiceMultiKeysadd_9_revoke_4_threshold_4_timestamp/server/metadata/timestamp.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "ec1d63e79fadec3dddb5852a1007fa7e5ed4b746b3d608ba9d3fa6fa2f25afa308a72474d820fe2b88adf4f9d6352e57fe7475725e539858f1ab07ab7a5ca305"
+ },
+ {
+ "keyid": "06e78858e726b48732eaae08a5731465587d322f4e63c0fae62ec13537c0a1cb",
+ "sig": "c3021d40208c80b25034d7d7d29d2faeeb7b0e4e3238430a0f2c0435337d3b2c47e87f4ec2c5692dc2aec01dee8e72feea2b6f72cb17591aca1e5252fe2c4f0b"
+ },
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "dbe051e077a6d7ad54ad39c41e3fc497c65b8f5cd9ac9b9c7bf0900dba979614198922232fcd2ef66ec9d5e571b9e936cd997c41e74c55abce8448d13b178007"
+ },
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "76946f3caeba163ff8fcaa9892f92457a9531da85092ddff660d8af2859fc94ea714e1962ab704a4e028fe48b195b850d6c29d13cf668bb2bf41b0814229b402"
+ },
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "9f5c7234e8c67e15fa326bc693595b6ac75aacbec4c5c6bd07bb4c8d8e9bfb35d1ee121987629e45fbb2f4fdcd6593a1f4cd1231b361b89832dcc19be248f70a"
+ },
+ {
+ "keyid": "75b81ec1572cc1dd55f88f13f0f0217b4e7a25f1665deed40115b612ff9c0eb8",
+ "sig": "d33845b0bfc6ace034377acc4e043dedd3c26c1d58b2f11bb3fa9416c86f55a396c3547d5f95662cbafe231af913752be851947413fca7fa5ec86948aae8ae0c"
+ },
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "f94b9c239f481f74715627accec1f2794faeefbc0a110e4ac4c8ab0f3451812042ea5b5f85b66b568ea2b00ac46b3d5496b184bc6a59fdee1361b6dbf5e6d90d"
+ },
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "b17e9fed31051906f6d31c81db44c025718ced629d2d2914e09c0da4f303163bbfbd11673491aafcce6e35226b3c2f1a6fefa3ff5f39b838d4c3d736e521ac07"
+ },
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "b82b9a726284bbc488f3fa8e7b91cd641b63aa0488c7706d7a8a09eecd9a12ce99e02fe77e0994084e72316476e26df42aac2d89616674e40c82a1c3f7082409"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2031-09-09T23:20:21Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "47ceec303641a009684536b381ee11129a338e4f8a0ea30f99efa205a78265de",
+ "sha512": "6d1e34a684483108365554f3c6996fba96f1f64892920436d2c4e27a399bb7b0cdd4fac07d1df250554ab2164e417be7391281b422a12ae26fb1dd619dc8a1c7"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/1.root.json
new file mode 100644
index 0000000..7e05abd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/1.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "5ff5c02ecdfc36583f4fb1a8e660bee1643f82a1a7cb3291739200b9f9be6ee026958af3b342e6d8d4f242a05f8e8558e146b6f692ddd4a7d558b85e82ad9b05"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "5ff5c02ecdfc36583f4fb1a8e660bee1643f82a1a7cb3291739200b9f9be6ee026958af3b342e6d8d4f242a05f8e8558e146b6f692ddd4a7d558b85e82ad9b05"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/root.json
new file mode 100644
index 0000000..7e05abd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "5ff5c02ecdfc36583f4fb1a8e660bee1643f82a1a7cb3291739200b9f9be6ee026958af3b342e6d8d4f242a05f8e8558e146b6f692ddd4a7d558b85e82ad9b05"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "5ff5c02ecdfc36583f4fb1a8e660bee1643f82a1a7cb3291739200b9f9be6ee026958af3b342e6d8d4f242a05f8e8558e146b6f692ddd4a7d558b85e82ad9b05"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/snapshot.json
new file mode 100644
index 0000000..e794f20
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "683aae02eb3fcc4fc0ffc37f2f43dc0fdb43e7997d3c65653cfdabcaea9fed8414e3d77047483eca96490747b2a62dec4d17021de42173520f12b60b39ffbc0f"
+ },
+ "length": 3832,
+ "version": 1
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e"
+ },
+ "length": 866,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "30374e6296c813037083321b16c0d2b9578de32a99f56e33134d1936ff14c611e50d0a68bf9e6a2f25dfb449e599f069c51f1c8e3113e36d905a6a0fe825a005"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "30374e6296c813037083321b16c0d2b9578de32a99f56e33134d1936ff14c611e50d0a68bf9e6a2f25dfb449e599f069c51f1c8e3113e36d905a6a0fe825a005"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/targets.json
new file mode 100644
index 0000000..60091e7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/targets.json
@@ -0,0 +1,28 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ }
+ },
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "method": "ed25519",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ },
+ {
+ "keyid": "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556",
+ "method": "ed25519",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/targets/0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/targets/0
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/targets/0
@@ -0,0 +1 @@
+0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/timestamp.json
new file mode 100644
index 0000000..9ba2ee4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/0/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "892f57086442891029dd180f01c8653ab91d0ddba2ab6002437634ad0a3579e31e21ef8790577086773ba8b5ffc1da80451589dae92ae122d2943a963fae9725"
+ },
+ "length": 1125,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "method": "ed25519",
+ "sig": "f661d41e29185709f03c5fc28498c369153df6be0eee08d11df869046a5b6605471729541ce8d05d9d5f035adac00d531efe6294c99715270c7e8acc5967fb0a"
+ },
+ {
+ "keyid": "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d",
+ "method": "ed25519",
+ "sig": "f661d41e29185709f03c5fc28498c369153df6be0eee08d11df869046a5b6605471729541ce8d05d9d5f035adac00d531efe6294c99715270c7e8acc5967fb0a"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/1.root.json
new file mode 120000
index 0000000..0ffd2b6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/1.root.json
@@ -0,0 +1 @@
+../../0/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/2.root.json
new file mode 100644
index 0000000..efd2263
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/2.root.json
@@ -0,0 +1,143 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "61cfd8782175d6b88a05088ad57c4147d56328bfeac7a8d2f135e831bb1d9f37eaa32e61405baabec61ae280f5ca47af21a3b2a6ef9866889a0d5eb4ce00fa08"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "61cfd8782175d6b88a05088ad57c4147d56328bfeac7a8d2f135e831bb1d9f37eaa32e61405baabec61ae280f5ca47af21a3b2a6ef9866889a0d5eb4ce00fa08"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "855ab125077b6a8e00f8224ef9e8bd595d8b5baeae7a4588605702b4279030f6a34dfa4ba544d6c66fcbec92dc7dc4d5df4f744b7b9962b281a24f7e5c264905"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "855ab125077b6a8e00f8224ef9e8bd595d8b5baeae7a4588605702b4279030f6a34dfa4ba544d6c66fcbec92dc7dc4d5df4f744b7b9962b281a24f7e5c264905"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/root.json
new file mode 100644
index 0000000..efd2263
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/root.json
@@ -0,0 +1,143 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "61cfd8782175d6b88a05088ad57c4147d56328bfeac7a8d2f135e831bb1d9f37eaa32e61405baabec61ae280f5ca47af21a3b2a6ef9866889a0d5eb4ce00fa08"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "61cfd8782175d6b88a05088ad57c4147d56328bfeac7a8d2f135e831bb1d9f37eaa32e61405baabec61ae280f5ca47af21a3b2a6ef9866889a0d5eb4ce00fa08"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "855ab125077b6a8e00f8224ef9e8bd595d8b5baeae7a4588605702b4279030f6a34dfa4ba544d6c66fcbec92dc7dc4d5df4f744b7b9962b281a24f7e5c264905"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "855ab125077b6a8e00f8224ef9e8bd595d8b5baeae7a4588605702b4279030f6a34dfa4ba544d6c66fcbec92dc7dc4d5df4f744b7b9962b281a24f7e5c264905"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/snapshot.json
new file mode 100644
index 0000000..bcf536b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "4feab9708b19ebd38844cab9879fb6c9c52b6a55c4281030149f696090d4e4ca07ebcaf7c97d6e13f41bae6c332242dda18ebeb63fb2555d759b2283cf8187ab"
+ },
+ "length": 4340,
+ "version": 2
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22"
+ },
+ "length": 1067,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "9d6b3455d3749f21974ccfe087044ca46254c84e6d0b8a0767e001a4eb6bb2bee19f341cec0c4f752deebd657fdeea7641df661c6a9d224e1d1fe8cee5826203"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "9d6b3455d3749f21974ccfe087044ca46254c84e6d0b8a0767e001a4eb6bb2bee19f341cec0c4f752deebd657fdeea7641df661c6a9d224e1d1fe8cee5826203"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets.json
new file mode 100644
index 0000000..eeb97eb
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets.json
@@ -0,0 +1,34 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ }
+ },
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "method": "ed25519",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ },
+ {
+ "keyid": "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556",
+ "method": "ed25519",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets/0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets/0
new file mode 120000
index 0000000..aced687
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets/0
@@ -0,0 +1 @@
+../../../0/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets/1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets/1
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/targets/1
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/timestamp.json
new file mode 100644
index 0000000..7c58b00
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/1/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "c94cf74bfd65605e8310957476c6cad2102bf9d3c76711a3530ed8e5f38289474fb01ac5747b4977d3ff0a4ddbb3f6767231c1ba54c78a708969807a2d587119"
+ },
+ "length": 1126,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "method": "ed25519",
+ "sig": "08dc93ecb35c64fe26e319b8a7cb06117d96257c96e4781960f8f0b158f559ec756f8affaafd9b6404494b00525e33b3cd2130f8ea2cb5990e6e5461025ea60c"
+ },
+ {
+ "keyid": "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d",
+ "method": "ed25519",
+ "sig": "08dc93ecb35c64fe26e319b8a7cb06117d96257c96e4781960f8f0b158f559ec756f8affaafd9b6404494b00525e33b3cd2130f8ea2cb5990e6e5461025ea60c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/1.root.json
new file mode 120000
index 0000000..cd73051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/1.root.json
@@ -0,0 +1 @@
+../../1/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/2.root.json
new file mode 120000
index 0000000..bbdcb12
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/2.root.json
@@ -0,0 +1 @@
+../../1/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/3.root.json
new file mode 100644
index 0000000..af419a2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/3.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "f88ad025e4e486cba6fc97d0a28bc9eb25d8f2148f66fb1929a8c906d5cc7753849ff91a56ade22e4a9d8729347617b75cbc34a5769b1cf1db8cbf2137168c0e"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "f88ad025e4e486cba6fc97d0a28bc9eb25d8f2148f66fb1929a8c906d5cc7753849ff91a56ade22e4a9d8729347617b75cbc34a5769b1cf1db8cbf2137168c0e"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/root.json
new file mode 100644
index 0000000..af419a2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "f88ad025e4e486cba6fc97d0a28bc9eb25d8f2148f66fb1929a8c906d5cc7753849ff91a56ade22e4a9d8729347617b75cbc34a5769b1cf1db8cbf2137168c0e"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "f88ad025e4e486cba6fc97d0a28bc9eb25d8f2148f66fb1929a8c906d5cc7753849ff91a56ade22e4a9d8729347617b75cbc34a5769b1cf1db8cbf2137168c0e"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/snapshot.json
new file mode 100644
index 0000000..cc575cd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "baf96d1da4a27b3e57dc35fa1ddde8b20a18f96f6268e07252ed4840725f599d4375ed99d9509f2edde04309efbba71c86735ba630698401cc1778cc31e768f4"
+ },
+ "length": 3832,
+ "version": 3
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4"
+ },
+ "length": 1268,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "4f202ab758b9a2baefbd23d8fea5deaa8c66d967afa9c52d81df2c17915fc10b059775d53ea858da2c668735f1eb1692f04e2f971545f4ba631f41579e729903"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "4f202ab758b9a2baefbd23d8fea5deaa8c66d967afa9c52d81df2c17915fc10b059775d53ea858da2c668735f1eb1692f04e2f971545f4ba631f41579e729903"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets.json
new file mode 100644
index 0000000..147dfc6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets.json
@@ -0,0 +1,40 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ }
+ },
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/0
new file mode 120000
index 0000000..2ac1d8b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/0
@@ -0,0 +1 @@
+../../../1/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/1
new file mode 120000
index 0000000..8e78bc3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/1
@@ -0,0 +1 @@
+../../../1/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/2
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/targets/2
@@ -0,0 +1 @@
+2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/timestamp.json
new file mode 100644
index 0000000..ffcf0eb
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/2/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "27cf44e990df07e40205463d25a12bbacd2ee3dc2650d1a01a37fa0ac27a5e2c885a6e85c2b07e8f56f8bd165cdcd756b182ccfb7f2728709b7a77a7b2251e86"
+ },
+ "length": 1126,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "method": "ed25519",
+ "sig": "9d1c694c4037e0f7d9665ae450ba1b3be30f0a882942802991986f3211788fc347edba9b90a6eeed54c20d9bf5ed9434b0e088a79d7ad2185d16a1b8589bce0f"
+ },
+ {
+ "keyid": "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d",
+ "method": "ed25519",
+ "sig": "9d1c694c4037e0f7d9665ae450ba1b3be30f0a882942802991986f3211788fc347edba9b90a6eeed54c20d9bf5ed9434b0e088a79d7ad2185d16a1b8589bce0f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/1.root.json
new file mode 120000
index 0000000..fb4fe81
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/1.root.json
@@ -0,0 +1 @@
+../../2/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/2.root.json
new file mode 120000
index 0000000..55f8b9d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/2.root.json
@@ -0,0 +1 @@
+../../2/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/3.root.json
new file mode 120000
index 0000000..b4564b7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/3.root.json
@@ -0,0 +1 @@
+../../2/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/4.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/4.root.json
new file mode 100644
index 0000000..c0966ce
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/4.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "780d8db1d802ef7ccaa23ae9bcccf7b619d820a505ae75f61453ebbc882ce4dff37194ee112f3da22feba8cc89531329872e22704a75a21658a5ec38ee568c00"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "780d8db1d802ef7ccaa23ae9bcccf7b619d820a505ae75f61453ebbc882ce4dff37194ee112f3da22feba8cc89531329872e22704a75a21658a5ec38ee568c00"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/root.json
new file mode 100644
index 0000000..c0966ce
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "780d8db1d802ef7ccaa23ae9bcccf7b619d820a505ae75f61453ebbc882ce4dff37194ee112f3da22feba8cc89531329872e22704a75a21658a5ec38ee568c00"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "780d8db1d802ef7ccaa23ae9bcccf7b619d820a505ae75f61453ebbc882ce4dff37194ee112f3da22feba8cc89531329872e22704a75a21658a5ec38ee568c00"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/snapshot.json
new file mode 100644
index 0000000..788252b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "f23ff2acc0678a3e5d75d192417e3d008c35aa1143fb65807c46310b28a0c2cb88c94fc8d29bef3bebf81ef1c32db0cec6f5f00359c6259f22346a252ddf664d"
+ },
+ "length": 3832,
+ "version": 4
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461"
+ },
+ "length": 1469,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "a076e6f99019484fc5daf715a62967e23a2ee94e8847298f5bc8ca21d0ae91207f0098dbd368c287d4468142c2ce29a74c9cecff0368a09673af1b2b0313dd00"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "a076e6f99019484fc5daf715a62967e23a2ee94e8847298f5bc8ca21d0ae91207f0098dbd368c287d4468142c2ce29a74c9cecff0368a09673af1b2b0313dd00"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets.json
new file mode 100644
index 0000000..c960d1c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets.json
@@ -0,0 +1,46 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ }
+ },
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/0
new file mode 120000
index 0000000..2e6dba0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/0
@@ -0,0 +1 @@
+../../../2/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/1
new file mode 120000
index 0000000..14fee5d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/1
@@ -0,0 +1 @@
+../../../2/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/2
new file mode 120000
index 0000000..dcd19e0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/2
@@ -0,0 +1 @@
+../../../2/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/3
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/targets/3
@@ -0,0 +1 @@
+3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/timestamp.json
new file mode 100644
index 0000000..2d62fa2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/3/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "3e1518698609a8ea5dc88323cbc3c7f487346cf489db59ff6c74d232372c3d77217416c5ccbdcf3172cb79df93ed635c3981663f2019f958a6f6a84cd443aee3"
+ },
+ "length": 1126,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "method": "ed25519",
+ "sig": "87d09d36cd148d9e7947353514818cb1ae455962acec5f98c1d8941755d1a515bc8f0cdde4602fd2eb25999c2e842f60882fa1e8d9dc97dbf6442e6f4c8c4e0e"
+ },
+ {
+ "keyid": "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d",
+ "method": "ed25519",
+ "sig": "87d09d36cd148d9e7947353514818cb1ae455962acec5f98c1d8941755d1a515bc8f0cdde4602fd2eb25999c2e842f60882fa1e8d9dc97dbf6442e6f4c8c4e0e"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/1.root.json
new file mode 120000
index 0000000..741d218
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/1.root.json
@@ -0,0 +1 @@
+../../3/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/2.root.json
new file mode 120000
index 0000000..fb9b350
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/2.root.json
@@ -0,0 +1 @@
+../../3/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/3.root.json
new file mode 120000
index 0000000..65e6c5b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/3.root.json
@@ -0,0 +1 @@
+../../3/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/4.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/4.root.json
new file mode 120000
index 0000000..c3662ce
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/4.root.json
@@ -0,0 +1 @@
+../../3/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/5.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/5.root.json
new file mode 100644
index 0000000..75f57ce
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/5.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "b9bec20a6e47c15ee0abae1ec2314bf129d838c0996be90dfa5ef5983354d2c55b83ddf2fbd9d783fb82d16f4b1106af8b01331bbd9648807e42f0eaafecb600"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "b9bec20a6e47c15ee0abae1ec2314bf129d838c0996be90dfa5ef5983354d2c55b83ddf2fbd9d783fb82d16f4b1106af8b01331bbd9648807e42f0eaafecb600"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/root.json
new file mode 100644
index 0000000..75f57ce
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "b9bec20a6e47c15ee0abae1ec2314bf129d838c0996be90dfa5ef5983354d2c55b83ddf2fbd9d783fb82d16f4b1106af8b01331bbd9648807e42f0eaafecb600"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "b9bec20a6e47c15ee0abae1ec2314bf129d838c0996be90dfa5ef5983354d2c55b83ddf2fbd9d783fb82d16f4b1106af8b01331bbd9648807e42f0eaafecb600"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/snapshot.json
new file mode 100644
index 0000000..8f96582
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "1d328734fe2f05dcf8527fd56dc2be8eb93f0c55f71d14aa6e8ad756d9d0a9dd41355df2712aaf992adc50add64ffde009a56cde02c4792e8c2d3b694ac70fc3"
+ },
+ "length": 3832,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db"
+ },
+ "length": 1670,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "e7b397e79f9fe7c12a662abbc99424b615790c64065e1ed66f6ce46d48ac0132a8c90e93ae3e7bfe5a75279cd015dbf52ef1a73c543697f1de5bb059072b7d02"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "e7b397e79f9fe7c12a662abbc99424b615790c64065e1ed66f6ce46d48ac0132a8c90e93ae3e7bfe5a75279cd015dbf52ef1a73c543697f1de5bb059072b7d02"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets.json
new file mode 100644
index 0000000..7dc9094
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets.json
@@ -0,0 +1,52 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ }
+ },
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/0
new file mode 120000
index 0000000..efe18e4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/0
@@ -0,0 +1 @@
+../../../3/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/1
new file mode 120000
index 0000000..b4a40c4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/1
@@ -0,0 +1 @@
+../../../3/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/2
new file mode 120000
index 0000000..1c68171
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/2
@@ -0,0 +1 @@
+../../../3/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/3
new file mode 120000
index 0000000..1dc4d27
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/3
@@ -0,0 +1 @@
+../../../3/repository/targets/3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/4 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/4
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/targets/4
@@ -0,0 +1 @@
+4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/timestamp.json
new file mode 100644
index 0000000..0119345
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/4/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "f3285e9249d6fe5c693a9766c8f9efbfb915349213f155cfc7e511e40fc43134b9925c589efb9cc561edf3bea182e4b61e23dc8434c00e0bf1a35dea1a7c81d9"
+ },
+ "length": 1126,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "method": "ed25519",
+ "sig": "efeb3f5a53fd6c6c9e03cb3ac48dccc9a7ff050108e89dc85d83b8ad56d7b752174c0d0ee60621982da9858c8d468844cc0f3d011ff0b121e58f34b6af1a1f0b"
+ },
+ {
+ "keyid": "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d",
+ "method": "ed25519",
+ "sig": "efeb3f5a53fd6c6c9e03cb3ac48dccc9a7ff050108e89dc85d83b8ad56d7b752174c0d0ee60621982da9858c8d468844cc0f3d011ff0b121e58f34b6af1a1f0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/1.root.json
new file mode 120000
index 0000000..fb796e3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/1.root.json
@@ -0,0 +1 @@
+../../4/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/2.root.json
new file mode 120000
index 0000000..5a3e454
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/2.root.json
@@ -0,0 +1 @@
+../../4/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/3.root.json
new file mode 120000
index 0000000..d1cb251
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/3.root.json
@@ -0,0 +1 @@
+../../4/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/4.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/4.root.json
new file mode 120000
index 0000000..bda6d08
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/4.root.json
@@ -0,0 +1 @@
+../../4/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/5.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/5.root.json
new file mode 120000
index 0000000..f99552a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/5.root.json
@@ -0,0 +1 @@
+../../4/repository/5.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/root.json
new file mode 120000
index 0000000..3d14058
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/root.json
@@ -0,0 +1 @@
+../../4/repository/root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/snapshot.json
new file mode 100644
index 0000000..96ad437
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "1d328734fe2f05dcf8527fd56dc2be8eb93f0c55f71d14aa6e8ad756d9d0a9dd41355df2712aaf992adc50add64ffde009a56cde02c4792e8c2d3b694ac70fc3"
+ },
+ "length": 3832,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "8ec9b98766edfe6acc23bb700183532a990b966ceb7b62784f6c80d4b1bfe5661df3cc2765b57bd0e78eb4b5c2b136f5ab7a365665c6e918d4137b83cb7e2262"
+ },
+ "length": 1871,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "cf36c85855679c11e0efb1cc53deb1a1bfd43d02e8fb564e6c280f1ef280a62fe05368336fdbe59daa642313b28479accfed25f4a3c8be9cc1d0474ab87c150f"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "cf36c85855679c11e0efb1cc53deb1a1bfd43d02e8fb564e6c280f1ef280a62fe05368336fdbe59daa642313b28479accfed25f4a3c8be9cc1d0474ab87c150f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets.json
new file mode 100644
index 0000000..3f07d3d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets.json
@@ -0,0 +1,58 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ },
+ "5": {
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ },
+ "length": 1
+ }
+ },
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/0
new file mode 120000
index 0000000..94cc287
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/0
@@ -0,0 +1 @@
+../../../4/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/1
new file mode 120000
index 0000000..51c7c9a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/1
@@ -0,0 +1 @@
+../../../4/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/2
new file mode 120000
index 0000000..46e3032
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/2
@@ -0,0 +1 @@
+../../../4/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/3
new file mode 120000
index 0000000..ca268e9
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/3
@@ -0,0 +1 @@
+../../../4/repository/targets/3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/4 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/4
new file mode 120000
index 0000000..19339ad
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/4
@@ -0,0 +1 @@
+../../../4/repository/targets/4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/5 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/5
new file mode 100644
index 0000000..7813681
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/targets/5
@@ -0,0 +1 @@
+5 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/timestamp.json
new file mode 100644
index 0000000..00124e2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-false/5/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "416d5114cfe34e8412cc68c85a1aaded17f53d8ba77f4c3fa769835904ae664d729711e88e1a90c2a6bc4f3c61e46eb026c0a1da5a22c7d28a3103c82c6b284a"
+ },
+ "length": 1126,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "method": "ed25519",
+ "sig": "d238cb69543dc66c40e5f9d5bcf58d43e6c99e5559399be52c08f6a7c29809a4d7c50014ea631f871b5e2be28264b0c682eafe775297888bc883061214c55600"
+ },
+ {
+ "keyid": "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d",
+ "method": "ed25519",
+ "sig": "d238cb69543dc66c40e5f9d5bcf58d43e6c99e5559399be52c08f6a7c29809a4d7c50014ea631f871b5e2be28264b0c682eafe775297888bc883061214c55600"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.root.json
new file mode 100644
index 0000000..93189d2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "485c97e0bb392ee60f9644cdd430f1325cb2329a8c0223fc69b209061d8f3a72268a087f4a715f5a0f70f967edaecebdcc8e9fc914ea8aff1c9741d495f5f904"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "485c97e0bb392ee60f9644cdd430f1325cb2329a8c0223fc69b209061d8f3a72268a087f4a715f5a0f70f967edaecebdcc8e9fc914ea8aff1c9741d495f5f904"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.snapshot.json
new file mode 100644
index 0000000..7bf654d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3"
+ },
+ "length": 3831,
+ "version": 1
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e"
+ },
+ "length": 866,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "f2ee59f04d66fa5a73249e985bae22e04bad839131334b630a2bd57566a2da2e5c32179c147daac927909a88a4896cc541dc30129450549c63b41bb5f0506e0d"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "f2ee59f04d66fa5a73249e985bae22e04bad839131334b630a2bd57566a2da2e5c32179c147daac927909a88a4896cc541dc30129450549c63b41bb5f0506e0d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.targets.json
new file mode 100644
index 0000000..60091e7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/1.targets.json
@@ -0,0 +1,28 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ }
+ },
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "method": "ed25519",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ },
+ {
+ "keyid": "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556",
+ "method": "ed25519",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
new file mode 100644
index 0000000..7bf654d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3"
+ },
+ "length": 3831,
+ "version": 1
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e"
+ },
+ "length": 866,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "f2ee59f04d66fa5a73249e985bae22e04bad839131334b630a2bd57566a2da2e5c32179c147daac927909a88a4896cc541dc30129450549c63b41bb5f0506e0d"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "f2ee59f04d66fa5a73249e985bae22e04bad839131334b630a2bd57566a2da2e5c32179c147daac927909a88a4896cc541dc30129450549c63b41bb5f0506e0d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
new file mode 100644
index 0000000..60091e7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
@@ -0,0 +1,28 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ }
+ },
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "method": "ed25519",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ },
+ {
+ "keyid": "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556",
+ "method": "ed25519",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
new file mode 100644
index 0000000..93189d2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "485c97e0bb392ee60f9644cdd430f1325cb2329a8c0223fc69b209061d8f3a72268a087f4a715f5a0f70f967edaecebdcc8e9fc914ea8aff1c9741d495f5f904"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "485c97e0bb392ee60f9644cdd430f1325cb2329a8c0223fc69b209061d8f3a72268a087f4a715f5a0f70f967edaecebdcc8e9fc914ea8aff1c9741d495f5f904"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/root.json
new file mode 100644
index 0000000..93189d2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "485c97e0bb392ee60f9644cdd430f1325cb2329a8c0223fc69b209061d8f3a72268a087f4a715f5a0f70f967edaecebdcc8e9fc914ea8aff1c9741d495f5f904"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "485c97e0bb392ee60f9644cdd430f1325cb2329a8c0223fc69b209061d8f3a72268a087f4a715f5a0f70f967edaecebdcc8e9fc914ea8aff1c9741d495f5f904"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/snapshot.json
new file mode 100644
index 0000000..7bf654d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3"
+ },
+ "length": 3831,
+ "version": 1
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e"
+ },
+ "length": 866,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "f2ee59f04d66fa5a73249e985bae22e04bad839131334b630a2bd57566a2da2e5c32179c147daac927909a88a4896cc541dc30129450549c63b41bb5f0506e0d"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "f2ee59f04d66fa5a73249e985bae22e04bad839131334b630a2bd57566a2da2e5c32179c147daac927909a88a4896cc541dc30129450549c63b41bb5f0506e0d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets.json
new file mode 100644
index 0000000..60091e7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets.json
@@ -0,0 +1,28 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ }
+ },
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "method": "ed25519",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ },
+ {
+ "keyid": "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556",
+ "method": "ed25519",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets/0.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets/0.0
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets/0.0
@@ -0,0 +1 @@
+0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/timestamp.json
new file mode 100644
index 0000000..ca0d4e3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/0/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4"
+ },
+ "length": 1125,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ },
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "method": "ed25519",
+ "sig": "1b51547737f70f5a3c4ee95c4ae9698d571777dd8b0a3c5d57af266beb95802889b3d7ba9062ff06e85427f05b76ad193d5f3ca45e1be86a266e53b2d0526504"
+ },
+ {
+ "keyid": "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d",
+ "method": "ed25519",
+ "sig": "1b51547737f70f5a3c4ee95c4ae9698d571777dd8b0a3c5d57af266beb95802889b3d7ba9062ff06e85427f05b76ad193d5f3ca45e1be86a266e53b2d0526504"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.root.json
new file mode 120000
index 0000000..0ffd2b6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.root.json
@@ -0,0 +1 @@
+../../0/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.snapshot.json
new file mode 120000
index 0000000..840f843
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../0/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.targets.json
new file mode 120000
index 0000000..4d11148
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/1.targets.json
@@ -0,0 +1 @@
+../../0/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
new file mode 100644
index 0000000..eeb97eb
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
@@ -0,0 +1,34 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ }
+ },
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "method": "ed25519",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ },
+ {
+ "keyid": "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556",
+ "method": "ed25519",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.root.json
new file mode 100644
index 0000000..911c87f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.root.json
@@ -0,0 +1,143 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "eaca65a09fb8f16ebc7c4560e9d23875225c0fd0f84747542a23e2dd9942fcf4c2bebd7292f06e1eaaf242048103594107353b75eea161bfe15ab24bfd752706"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "eaca65a09fb8f16ebc7c4560e9d23875225c0fd0f84747542a23e2dd9942fcf4c2bebd7292f06e1eaaf242048103594107353b75eea161bfe15ab24bfd752706"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "0dd9685391cc229d15c468fb3a71d93780bf1220b82e97c5dc6384db76eab74b9254478c89c26f5cf86f640e6a49d0a9d6990ba151fb862648181c8c029f470d"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "0dd9685391cc229d15c468fb3a71d93780bf1220b82e97c5dc6384db76eab74b9254478c89c26f5cf86f640e6a49d0a9d6990ba151fb862648181c8c029f470d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.snapshot.json
new file mode 100644
index 0000000..43bfdc1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098"
+ },
+ "length": 4339,
+ "version": 2
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22"
+ },
+ "length": 1067,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "f5b71b0dfefa020f7110d8767d5a6b7a98f6a71a89a3819cd54ae628081502b3bd18e8c7f1254f53a5346088deaee66fdd359ae365ea3758f0dc0032e9517a05"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "f5b71b0dfefa020f7110d8767d5a6b7a98f6a71a89a3819cd54ae628081502b3bd18e8c7f1254f53a5346088deaee66fdd359ae365ea3758f0dc0032e9517a05"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.targets.json
new file mode 100644
index 0000000..eeb97eb
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/2.targets.json
@@ -0,0 +1,34 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ }
+ },
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "method": "ed25519",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ },
+ {
+ "keyid": "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556",
+ "method": "ed25519",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
new file mode 100644
index 0000000..43bfdc1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098"
+ },
+ "length": 4339,
+ "version": 2
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22"
+ },
+ "length": 1067,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "f5b71b0dfefa020f7110d8767d5a6b7a98f6a71a89a3819cd54ae628081502b3bd18e8c7f1254f53a5346088deaee66fdd359ae365ea3758f0dc0032e9517a05"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "f5b71b0dfefa020f7110d8767d5a6b7a98f6a71a89a3819cd54ae628081502b3bd18e8c7f1254f53a5346088deaee66fdd359ae365ea3758f0dc0032e9517a05"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
new file mode 120000
index 0000000..027671c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
@@ -0,0 +1 @@
+../../0/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
new file mode 120000
index 0000000..aecd947
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
@@ -0,0 +1 @@
+../../0/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
new file mode 100644
index 0000000..911c87f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
@@ -0,0 +1,143 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "eaca65a09fb8f16ebc7c4560e9d23875225c0fd0f84747542a23e2dd9942fcf4c2bebd7292f06e1eaaf242048103594107353b75eea161bfe15ab24bfd752706"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "eaca65a09fb8f16ebc7c4560e9d23875225c0fd0f84747542a23e2dd9942fcf4c2bebd7292f06e1eaaf242048103594107353b75eea161bfe15ab24bfd752706"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "0dd9685391cc229d15c468fb3a71d93780bf1220b82e97c5dc6384db76eab74b9254478c89c26f5cf86f640e6a49d0a9d6990ba151fb862648181c8c029f470d"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "0dd9685391cc229d15c468fb3a71d93780bf1220b82e97c5dc6384db76eab74b9254478c89c26f5cf86f640e6a49d0a9d6990ba151fb862648181c8c029f470d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
new file mode 120000
index 0000000..70b171e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
@@ -0,0 +1 @@
+../../0/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/root.json
new file mode 100644
index 0000000..911c87f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/root.json
@@ -0,0 +1,143 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "method": "ed25519",
+ "sig": "eaca65a09fb8f16ebc7c4560e9d23875225c0fd0f84747542a23e2dd9942fcf4c2bebd7292f06e1eaaf242048103594107353b75eea161bfe15ab24bfd752706"
+ },
+ {
+ "keyid": "f806b84c8763453d011c0ba830a3290ee4be587969f5325117c7626411a0f953",
+ "method": "ed25519",
+ "sig": "eaca65a09fb8f16ebc7c4560e9d23875225c0fd0f84747542a23e2dd9942fcf4c2bebd7292f06e1eaaf242048103594107353b75eea161bfe15ab24bfd752706"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "0dd9685391cc229d15c468fb3a71d93780bf1220b82e97c5dc6384db76eab74b9254478c89c26f5cf86f640e6a49d0a9d6990ba151fb862648181c8c029f470d"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "0dd9685391cc229d15c468fb3a71d93780bf1220b82e97c5dc6384db76eab74b9254478c89c26f5cf86f640e6a49d0a9d6990ba151fb862648181c8c029f470d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/snapshot.json
new file mode 100644
index 0000000..43bfdc1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098"
+ },
+ "length": 4339,
+ "version": 2
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22"
+ },
+ "length": 1067,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "f5b71b0dfefa020f7110d8767d5a6b7a98f6a71a89a3819cd54ae628081502b3bd18e8c7f1254f53a5346088deaee66fdd359ae365ea3758f0dc0032e9517a05"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "f5b71b0dfefa020f7110d8767d5a6b7a98f6a71a89a3819cd54ae628081502b3bd18e8c7f1254f53a5346088deaee66fdd359ae365ea3758f0dc0032e9517a05"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets.json
new file mode 100644
index 0000000..eeb97eb
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets.json
@@ -0,0 +1,34 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ }
+ },
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "method": "ed25519",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ },
+ {
+ "keyid": "d77999c3b2d553fceee309cc47bf7ab2fee37b8a0b30d4033e4b40dc09719556",
+ "method": "ed25519",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/0.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/0.0
new file mode 120000
index 0000000..0a5640e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/0.0
@@ -0,0 +1 @@
+../../../0/repository/targets/0.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/0.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/0.1
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/0.1
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..b0c459f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/timestamp.json
new file mode 100644
index 0000000..884a54b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/1/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9"
+ },
+ "length": 1126,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ },
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "method": "ed25519",
+ "sig": "c077ad4567c1db81609c89ab80310de559a2045d58a7f0166269f82e6d33a326248414ff7e5eb2d876d7741652ea1b6fab61f9bc774a6592727ac248a196720f"
+ },
+ {
+ "keyid": "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d",
+ "method": "ed25519",
+ "sig": "c077ad4567c1db81609c89ab80310de559a2045d58a7f0166269f82e6d33a326248414ff7e5eb2d876d7741652ea1b6fab61f9bc774a6592727ac248a196720f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.root.json
new file mode 120000
index 0000000..cd73051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.root.json
@@ -0,0 +1 @@
+../../1/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.snapshot.json
new file mode 120000
index 0000000..1307f57
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../1/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.targets.json
new file mode 120000
index 0000000..85e1ecc
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/1.targets.json
@@ -0,0 +1 @@
+../../1/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
new file mode 120000
index 0000000..0018c82
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
@@ -0,0 +1 @@
+../../1/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json
new file mode 100644
index 0000000..c6303cd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94"
+ },
+ "length": 3831,
+ "version": 3
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4"
+ },
+ "length": 1268,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "b3aa3119a74147ceb37b98248638710da5741c635e5791b13fd851b37212e4d3d5e7f6cbd4122b232a4010c13332c3048c1c6cc3e2838934ed481e6959441f05"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "b3aa3119a74147ceb37b98248638710da5741c635e5791b13fd851b37212e4d3d5e7f6cbd4122b232a4010c13332c3048c1c6cc3e2838934ed481e6959441f05"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.root.json
new file mode 120000
index 0000000..bbdcb12
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.root.json
@@ -0,0 +1 @@
+../../1/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.snapshot.json
new file mode 120000
index 0000000..24d1075
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../1/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.targets.json
new file mode 120000
index 0000000..f6a1749
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/2.targets.json
@@ -0,0 +1 @@
+../../1/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
new file mode 120000
index 0000000..69cd01d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
@@ -0,0 +1 @@
+../../1/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.root.json
new file mode 100644
index 0000000..deacdc1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "d11082d75a4e60f9c5d1d176dfea4d7c43a41a9d4595a8b14b844a43c329bf86d2bf4eb8d6255af1afd97bbd5cfc1a69f038a82617cbfcfb82c6434e1790c702"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "d11082d75a4e60f9c5d1d176dfea4d7c43a41a9d4595a8b14b844a43c329bf86d2bf4eb8d6255af1afd97bbd5cfc1a69f038a82617cbfcfb82c6434e1790c702"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.snapshot.json
new file mode 100644
index 0000000..c6303cd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94"
+ },
+ "length": 3831,
+ "version": 3
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4"
+ },
+ "length": 1268,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "b3aa3119a74147ceb37b98248638710da5741c635e5791b13fd851b37212e4d3d5e7f6cbd4122b232a4010c13332c3048c1c6cc3e2838934ed481e6959441f05"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "b3aa3119a74147ceb37b98248638710da5741c635e5791b13fd851b37212e4d3d5e7f6cbd4122b232a4010c13332c3048c1c6cc3e2838934ed481e6959441f05"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.targets.json
new file mode 100644
index 0000000..147dfc6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/3.targets.json
@@ -0,0 +1,40 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ }
+ },
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
new file mode 120000
index 0000000..944d357
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
@@ -0,0 +1 @@
+../../1/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json
new file mode 100644
index 0000000..deacdc1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "d11082d75a4e60f9c5d1d176dfea4d7c43a41a9d4595a8b14b844a43c329bf86d2bf4eb8d6255af1afd97bbd5cfc1a69f038a82617cbfcfb82c6434e1790c702"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "d11082d75a4e60f9c5d1d176dfea4d7c43a41a9d4595a8b14b844a43c329bf86d2bf4eb8d6255af1afd97bbd5cfc1a69f038a82617cbfcfb82c6434e1790c702"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
new file mode 120000
index 0000000..de6ef37
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
@@ -0,0 +1 @@
+../../1/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
new file mode 120000
index 0000000..f8bb03c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
@@ -0,0 +1 @@
+../../1/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json
new file mode 100644
index 0000000..147dfc6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json
@@ -0,0 +1,40 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ }
+ },
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
new file mode 120000
index 0000000..ce078d7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
@@ -0,0 +1 @@
+../../1/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/root.json
new file mode 100644
index 0000000..deacdc1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "d11082d75a4e60f9c5d1d176dfea4d7c43a41a9d4595a8b14b844a43c329bf86d2bf4eb8d6255af1afd97bbd5cfc1a69f038a82617cbfcfb82c6434e1790c702"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "d11082d75a4e60f9c5d1d176dfea4d7c43a41a9d4595a8b14b844a43c329bf86d2bf4eb8d6255af1afd97bbd5cfc1a69f038a82617cbfcfb82c6434e1790c702"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/snapshot.json
new file mode 100644
index 0000000..c6303cd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94"
+ },
+ "length": 3831,
+ "version": 3
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4"
+ },
+ "length": 1268,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "method": "ed25519",
+ "sig": "b3aa3119a74147ceb37b98248638710da5741c635e5791b13fd851b37212e4d3d5e7f6cbd4122b232a4010c13332c3048c1c6cc3e2838934ed481e6959441f05"
+ },
+ {
+ "keyid": "7165b3cf2ad6a10d456498d958e80f2356b0d3a230d93a96a8dada5fdd8dd40d",
+ "method": "ed25519",
+ "sig": "b3aa3119a74147ceb37b98248638710da5741c635e5791b13fd851b37212e4d3d5e7f6cbd4122b232a4010c13332c3048c1c6cc3e2838934ed481e6959441f05"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets.json
new file mode 100644
index 0000000..147dfc6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets.json
@@ -0,0 +1,40 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ }
+ },
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.0
new file mode 120000
index 0000000..59c65e1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.0
@@ -0,0 +1 @@
+../../../1/repository/targets/0.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.1
new file mode 120000
index 0000000..37e8fbc
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.1
@@ -0,0 +1 @@
+../../../1/repository/targets/0.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.2
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/0.2
@@ -0,0 +1 @@
+2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..de9aac3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..2ebb2d3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/timestamp.json
new file mode 100644
index 0000000..2356947
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/2/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432"
+ },
+ "length": 1126,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ },
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "method": "ed25519",
+ "sig": "25122819bb1e9fcd7383d0a13a35a049470162140b1573eb139e4269e29ec31b77a9771396bf2c9719c9ddb4e167e2e5ebabfef229cb4f16b59db45c6fdb4508"
+ },
+ {
+ "keyid": "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d",
+ "method": "ed25519",
+ "sig": "25122819bb1e9fcd7383d0a13a35a049470162140b1573eb139e4269e29ec31b77a9771396bf2c9719c9ddb4e167e2e5ebabfef229cb4f16b59db45c6fdb4508"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.root.json
new file mode 120000
index 0000000..fb4fe81
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.root.json
@@ -0,0 +1 @@
+../../2/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.snapshot.json
new file mode 120000
index 0000000..4dfb0d0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.targets.json
new file mode 120000
index 0000000..1eeb564
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/1.targets.json
@@ -0,0 +1 @@
+../../2/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
new file mode 120000
index 0000000..32bd324
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
@@ -0,0 +1 @@
+../../2/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json
new file mode 100644
index 0000000..6671723
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa"
+ },
+ "length": 3831,
+ "version": 4
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461"
+ },
+ "length": 1469,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "4619db132982f96f2f7d62d1ea5e7bfdc69eab8cf10cc6eca8721557144a8b193b98f6c3a25da862c414da348d42cee78b59c3f14903be937b16b39e1003f503"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "4619db132982f96f2f7d62d1ea5e7bfdc69eab8cf10cc6eca8721557144a8b193b98f6c3a25da862c414da348d42cee78b59c3f14903be937b16b39e1003f503"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json
new file mode 120000
index 0000000..03468a1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.root.json
new file mode 120000
index 0000000..55f8b9d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.root.json
@@ -0,0 +1 @@
+../../2/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.snapshot.json
new file mode 120000
index 0000000..65445cb
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.targets.json
new file mode 120000
index 0000000..3600a0f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/2.targets.json
@@ -0,0 +1 @@
+../../2/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
new file mode 120000
index 0000000..b20325e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.root.json
new file mode 120000
index 0000000..b4564b7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.root.json
@@ -0,0 +1 @@
+../../2/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.snapshot.json
new file mode 120000
index 0000000..7d86b95
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.targets.json
new file mode 120000
index 0000000..d1df10e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/3.targets.json
@@ -0,0 +1 @@
+../../2/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
new file mode 120000
index 0000000..d137215
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.root.json
new file mode 100644
index 0000000..8ccb8d7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "5a5afbd4270a2638b84f50d0291e35acdf59e25492914017cb317c617d71c3a9c450d3f71f1dd7a33c369399d8de26f3a61060146682d84159f27c08f1675005"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "5a5afbd4270a2638b84f50d0291e35acdf59e25492914017cb317c617d71c3a9c450d3f71f1dd7a33c369399d8de26f3a61060146682d84159f27c08f1675005"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.snapshot.json
new file mode 100644
index 0000000..6671723
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa"
+ },
+ "length": 3831,
+ "version": 4
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461"
+ },
+ "length": 1469,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "4619db132982f96f2f7d62d1ea5e7bfdc69eab8cf10cc6eca8721557144a8b193b98f6c3a25da862c414da348d42cee78b59c3f14903be937b16b39e1003f503"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "4619db132982f96f2f7d62d1ea5e7bfdc69eab8cf10cc6eca8721557144a8b193b98f6c3a25da862c414da348d42cee78b59c3f14903be937b16b39e1003f503"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.targets.json
new file mode 100644
index 0000000..c960d1c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/4.targets.json
@@ -0,0 +1,46 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ }
+ },
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json
new file mode 120000
index 0000000..f966ccc
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json
@@ -0,0 +1 @@
+../../2/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
new file mode 120000
index 0000000..77a3656
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
@@ -0,0 +1 @@
+../../2/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json
new file mode 100644
index 0000000..8ccb8d7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "5a5afbd4270a2638b84f50d0291e35acdf59e25492914017cb317c617d71c3a9c450d3f71f1dd7a33c369399d8de26f3a61060146682d84159f27c08f1675005"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "5a5afbd4270a2638b84f50d0291e35acdf59e25492914017cb317c617d71c3a9c450d3f71f1dd7a33c369399d8de26f3a61060146682d84159f27c08f1675005"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json
new file mode 100644
index 0000000..c960d1c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json
@@ -0,0 +1,46 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ }
+ },
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
new file mode 120000
index 0000000..49199f1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
@@ -0,0 +1 @@
+../../2/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json
new file mode 120000
index 0000000..4e721c7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json
@@ -0,0 +1 @@
+../../2/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
new file mode 120000
index 0000000..f1f205a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
@@ -0,0 +1 @@
+../../2/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/root.json
new file mode 100644
index 0000000..8ccb8d7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "5a5afbd4270a2638b84f50d0291e35acdf59e25492914017cb317c617d71c3a9c450d3f71f1dd7a33c369399d8de26f3a61060146682d84159f27c08f1675005"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "5a5afbd4270a2638b84f50d0291e35acdf59e25492914017cb317c617d71c3a9c450d3f71f1dd7a33c369399d8de26f3a61060146682d84159f27c08f1675005"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/snapshot.json
new file mode 100644
index 0000000..6671723
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa"
+ },
+ "length": 3831,
+ "version": 4
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461"
+ },
+ "length": 1469,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "4619db132982f96f2f7d62d1ea5e7bfdc69eab8cf10cc6eca8721557144a8b193b98f6c3a25da862c414da348d42cee78b59c3f14903be937b16b39e1003f503"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "4619db132982f96f2f7d62d1ea5e7bfdc69eab8cf10cc6eca8721557144a8b193b98f6c3a25da862c414da348d42cee78b59c3f14903be937b16b39e1003f503"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets.json
new file mode 100644
index 0000000..c960d1c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets.json
@@ -0,0 +1,46 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ }
+ },
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.0
new file mode 120000
index 0000000..13920b0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.0
@@ -0,0 +1 @@
+../../../2/repository/targets/0.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.1
new file mode 120000
index 0000000..357ae1e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.1
@@ -0,0 +1 @@
+../../../2/repository/targets/0.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.2
new file mode 120000
index 0000000..20ee725
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.2
@@ -0,0 +1 @@
+../../../2/repository/targets/0.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.3
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/0.3
@@ -0,0 +1 @@
+3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..f820397
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..7cbcc2a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..180cdb4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/timestamp.json
new file mode 100644
index 0000000..ebdf9f8
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/3/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7"
+ },
+ "length": 1126,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ },
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "method": "ed25519",
+ "sig": "a14cd9dfc17e257aa7f5d6586e32defbc076d1aa3cb30fd86be362f0c528df3145aeba52f5eceb8fab481336b95667c9009fb639bfe996cbe11412c60226320a"
+ },
+ {
+ "keyid": "c7a2ca868b0e65b50cc6a2dfcfd9c129bdcb3da390ac78bb83a732be02ed5a5d",
+ "method": "ed25519",
+ "sig": "a14cd9dfc17e257aa7f5d6586e32defbc076d1aa3cb30fd86be362f0c528df3145aeba52f5eceb8fab481336b95667c9009fb639bfe996cbe11412c60226320a"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.root.json
new file mode 120000
index 0000000..741d218
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.root.json
@@ -0,0 +1 @@
+../../3/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.snapshot.json
new file mode 120000
index 0000000..f87f7ed
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.targets.json
new file mode 120000
index 0000000..b0002ec
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/1.targets.json
@@ -0,0 +1 @@
+../../3/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
new file mode 120000
index 0000000..71b4225
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
@@ -0,0 +1 @@
+../../3/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json
new file mode 120000
index 0000000..11937d8
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json
new file mode 120000
index 0000000..0e10c4d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.root.json
new file mode 120000
index 0000000..fb9b350
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.root.json
@@ -0,0 +1 @@
+../../3/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.snapshot.json
new file mode 120000
index 0000000..e96983b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.targets.json
new file mode 120000
index 0000000..dbb2a75
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/2.targets.json
@@ -0,0 +1 @@
+../../3/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
new file mode 120000
index 0000000..5780021
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.root.json
new file mode 120000
index 0000000..65e6c5b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.root.json
@@ -0,0 +1 @@
+../../3/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.snapshot.json
new file mode 120000
index 0000000..f2fd560
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.targets.json
new file mode 120000
index 0000000..ca61e77
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/3.targets.json
@@ -0,0 +1 @@
+../../3/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
new file mode 120000
index 0000000..98160f7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.root.json
new file mode 120000
index 0000000..c3662ce
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.root.json
@@ -0,0 +1 @@
+../../3/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.snapshot.json
new file mode 120000
index 0000000..a2f839a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.targets.json
new file mode 120000
index 0000000..9f7a784
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/4.targets.json
@@ -0,0 +1 @@
+../../3/repository/4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.root.json
new file mode 100644
index 0000000..a9f853e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "1d3e90aac8b4fad8846d3ae59ed9069ba335310bd14259a6ca6a905499a9b166761c3c737cbef6d231f84b78575d2cb543caba350a1fcaa104589c86cf2e890c"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "1d3e90aac8b4fad8846d3ae59ed9069ba335310bd14259a6ca6a905499a9b166761c3c737cbef6d231f84b78575d2cb543caba350a1fcaa104589c86cf2e890c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.snapshot.json
new file mode 100644
index 0000000..882ff3a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd"
+ },
+ "length": 3831,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db"
+ },
+ "length": 1670,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "1d473b59dec8a119fb8bc1e5b33c3b0896f62598d0b560cc7a98f501bdc9436fb2399219f8d0301081fe9db22f6734bd1b865a3b9040c3a5357e67d0dc89880f"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "1d473b59dec8a119fb8bc1e5b33c3b0896f62598d0b560cc7a98f501bdc9436fb2399219f8d0301081fe9db22f6734bd1b865a3b9040c3a5357e67d0dc89880f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.targets.json
new file mode 100644
index 0000000..7dc9094
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/5.targets.json
@@ -0,0 +1,52 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ }
+ },
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json
new file mode 120000
index 0000000..fcb3707
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json
@@ -0,0 +1 @@
+../../3/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
new file mode 120000
index 0000000..21e403f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
@@ -0,0 +1 @@
+../../3/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json
new file mode 120000
index 0000000..37511d0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json
@@ -0,0 +1 @@
+../../3/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json
new file mode 120000
index 0000000..29e8425
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json
@@ -0,0 +1 @@
+../../3/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json
new file mode 100644
index 0000000..a9f853e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "1d3e90aac8b4fad8846d3ae59ed9069ba335310bd14259a6ca6a905499a9b166761c3c737cbef6d231f84b78575d2cb543caba350a1fcaa104589c86cf2e890c"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "1d3e90aac8b4fad8846d3ae59ed9069ba335310bd14259a6ca6a905499a9b166761c3c737cbef6d231f84b78575d2cb543caba350a1fcaa104589c86cf2e890c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
new file mode 120000
index 0000000..26fca70
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
@@ -0,0 +1 @@
+../../3/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json
new file mode 120000
index 0000000..b978f58
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json
@@ -0,0 +1 @@
+../../3/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
new file mode 120000
index 0000000..c420672
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
@@ -0,0 +1 @@
+../../3/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json
new file mode 100644
index 0000000..7dc9094
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json
@@ -0,0 +1,52 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ }
+ },
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json
new file mode 100644
index 0000000..882ff3a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd"
+ },
+ "length": 3831,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db"
+ },
+ "length": 1670,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "1d473b59dec8a119fb8bc1e5b33c3b0896f62598d0b560cc7a98f501bdc9436fb2399219f8d0301081fe9db22f6734bd1b865a3b9040c3a5357e67d0dc89880f"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "1d473b59dec8a119fb8bc1e5b33c3b0896f62598d0b560cc7a98f501bdc9436fb2399219f8d0301081fe9db22f6734bd1b865a3b9040c3a5357e67d0dc89880f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/root.json
new file mode 100644
index 0000000..a9f853e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/root.json
@@ -0,0 +1,133 @@
+{
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ },
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "method": "ed25519",
+ "sig": "1d3e90aac8b4fad8846d3ae59ed9069ba335310bd14259a6ca6a905499a9b166761c3c737cbef6d231f84b78575d2cb543caba350a1fcaa104589c86cf2e890c"
+ },
+ {
+ "keyid": "a67ca94ed559c97db79999d32b79ca35b5ae1795d2f6be3eafa3bd5816a8f2c4",
+ "method": "ed25519",
+ "sig": "1d3e90aac8b4fad8846d3ae59ed9069ba335310bd14259a6ca6a905499a9b166761c3c737cbef6d231f84b78575d2cb543caba350a1fcaa104589c86cf2e890c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/snapshot.json
new file mode 100644
index 0000000..882ff3a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd"
+ },
+ "length": 3831,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db"
+ },
+ "length": 1670,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "1d473b59dec8a119fb8bc1e5b33c3b0896f62598d0b560cc7a98f501bdc9436fb2399219f8d0301081fe9db22f6734bd1b865a3b9040c3a5357e67d0dc89880f"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "1d473b59dec8a119fb8bc1e5b33c3b0896f62598d0b560cc7a98f501bdc9436fb2399219f8d0301081fe9db22f6734bd1b865a3b9040c3a5357e67d0dc89880f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets.json
new file mode 100644
index 0000000..7dc9094
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets.json
@@ -0,0 +1,52 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ }
+ },
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.0
new file mode 120000
index 0000000..7561d7e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.0
@@ -0,0 +1 @@
+../../../3/repository/targets/0.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.1
new file mode 120000
index 0000000..ef5bca2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.1
@@ -0,0 +1 @@
+../../../3/repository/targets/0.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.2
new file mode 120000
index 0000000..26212c8
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.2
@@ -0,0 +1 @@
+../../../3/repository/targets/0.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.3
new file mode 120000
index 0000000..6a0a962
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.3
@@ -0,0 +1 @@
+../../../3/repository/targets/0.3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.4 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.4
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/0.4
@@ -0,0 +1 @@
+4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..8bca78f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 120000
index 0000000..814339f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+../../../3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..dc5c46d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..16b7967
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
@@ -0,0 +1 @@
+4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/timestamp.json
new file mode 100644
index 0000000..142621e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/4/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c"
+ },
+ "length": 1126,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ },
+ "signatures": [
+ {
+ "keyid": "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "method": "ed25519",
+ "sig": "82aa747d16c493b0b92c32de5832bf447d2f2348970f066598835d11efba303bc9685e3436200a88778d78a50111df81639d206c5e09b491989bec461a98550b"
+ },
+ {
+ "keyid": "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d",
+ "method": "ed25519",
+ "sig": "82aa747d16c493b0b92c32de5832bf447d2f2348970f066598835d11efba303bc9685e3436200a88778d78a50111df81639d206c5e09b491989bec461a98550b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.root.json
new file mode 120000
index 0000000..fb796e3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.root.json
@@ -0,0 +1 @@
+../../4/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.snapshot.json
new file mode 120000
index 0000000..f7fb098
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.targets.json
new file mode 120000
index 0000000..127f609
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/1.targets.json
@@ -0,0 +1 @@
+../../4/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
new file mode 120000
index 0000000..d1949f9
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json
@@ -0,0 +1 @@
+../../4/repository/118c2a2b6b9f7bc667dca8404a2671910a22b6fe48e460816a9f6f2be66f7806a789c9dc4880d2c2812c0bc7607caac30cc0a8299183fd7228e1049f6920ce22.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json
new file mode 120000
index 0000000..8106e59
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/148c273a895a85127bb254edd19da2190176272446cd0f43220aba2b0f72fe954a2eab52c38daf67b1c3cbe877848d01266d8a1af3e858c8d004682a3393f3a7.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json
new file mode 120000
index 0000000..1c73279
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/15c2c14fb300cecb52c9ef9a88545759028ec5d7e0f028269ba828c2199e3f0e56f963bdcfd0897c51ebb95ee2452666ec7f65bae688b48db1b8aa8de944e432.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.root.json
new file mode 120000
index 0000000..5a3e454
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.root.json
@@ -0,0 +1 @@
+../../4/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.snapshot.json
new file mode 120000
index 0000000..5b418d6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.targets.json
new file mode 120000
index 0000000..04b0be4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/2.targets.json
@@ -0,0 +1 @@
+../../4/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
new file mode 120000
index 0000000..237b697
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/23170400c30b4bbcd375c88ae22104241e377006e9c40c64ed49aef9ad2bdfa49ba2832ccb9c3776aaee336195de73cda28b5ba65fb468f7a5e60b870cbe26e9.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.root.json
new file mode 120000
index 0000000..d1cb251
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.root.json
@@ -0,0 +1 @@
+../../4/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.snapshot.json
new file mode 120000
index 0000000..d23c05c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.targets.json
new file mode 120000
index 0000000..2854154
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/3.targets.json
@@ -0,0 +1 @@
+../../4/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
new file mode 120000
index 0000000..aad4346
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/33b282c1970996734deff944f2862f84b196f0b2b67dfa7383f75f95ef3e8673602aa411e05572075729ead6caa35df6720ce5723fd67c63d4aaa81dd966c5a4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.root.json
new file mode 120000
index 0000000..bda6d08
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.root.json
@@ -0,0 +1 @@
+../../4/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.snapshot.json
new file mode 120000
index 0000000..b7aefb2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.targets.json
new file mode 120000
index 0000000..fef64e4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/4.targets.json
@@ -0,0 +1 @@
+../../4/repository/4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.root.json
new file mode 120000
index 0000000..f99552a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.root.json
@@ -0,0 +1 @@
+../../4/repository/5.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.snapshot.json
new file mode 120000
index 0000000..4e98537
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/5.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.targets.json
new file mode 120000
index 0000000..f877144
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/5.targets.json
@@ -0,0 +1 @@
+../../4/repository/5.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json
new file mode 120000
index 0000000..102fb8f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json
@@ -0,0 +1 @@
+../../4/repository/590c7d2df37495eae436e25eea548eb48294ef63aaeebe51b1bc1056c149fd54710c67d231c19c14708824aa7095e0028b6691fcf21c1edfcc7a88a48d4b7b94.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6.snapshot.json
new file mode 100644
index 0000000..15ab63b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd"
+ },
+ "length": 3831,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "8ec9b98766edfe6acc23bb700183532a990b966ceb7b62784f6c80d4b1bfe5661df3cc2765b57bd0e78eb4b5c2b136f5ab7a365665c6e918d4137b83cb7e2262"
+ },
+ "length": 1871,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "61f3a52657285ee76cac838dcf7bd605c8b895dadd7877f5ef3aaa8b22ec0e952fa44c9d9145d5740e56568d5c772c4ca46f96458c813d24c61596cfddff350b"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "61f3a52657285ee76cac838dcf7bd605c8b895dadd7877f5ef3aaa8b22ec0e952fa44c9d9145d5740e56568d5c772c4ca46f96458c813d24c61596cfddff350b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6.targets.json
new file mode 100644
index 0000000..3f07d3d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6.targets.json
@@ -0,0 +1,58 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ },
+ "5": {
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ },
+ "length": 1
+ }
+ },
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/63c16cc2225e77b7c94ec3b143271e02d97a1bfba828e11820392b9108412ef22f09732694673331f386b6f8a281103e20d07c21db78f487e1b046d9968173c8.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/63c16cc2225e77b7c94ec3b143271e02d97a1bfba828e11820392b9108412ef22f09732694673331f386b6f8a281103e20d07c21db78f487e1b046d9968173c8.snapshot.json
new file mode 100644
index 0000000..15ab63b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/63c16cc2225e77b7c94ec3b143271e02d97a1bfba828e11820392b9108412ef22f09732694673331f386b6f8a281103e20d07c21db78f487e1b046d9968173c8.snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd"
+ },
+ "length": 3831,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "8ec9b98766edfe6acc23bb700183532a990b966ceb7b62784f6c80d4b1bfe5661df3cc2765b57bd0e78eb4b5c2b136f5ab7a365665c6e918d4137b83cb7e2262"
+ },
+ "length": 1871,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "61f3a52657285ee76cac838dcf7bd605c8b895dadd7877f5ef3aaa8b22ec0e952fa44c9d9145d5740e56568d5c772c4ca46f96458c813d24c61596cfddff350b"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "61f3a52657285ee76cac838dcf7bd605c8b895dadd7877f5ef3aaa8b22ec0e952fa44c9d9145d5740e56568d5c772c4ca46f96458c813d24c61596cfddff350b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
new file mode 120000
index 0000000..18eed32
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json
@@ -0,0 +1 @@
+../../4/repository/6ecff211b4df20d84e5a99a1145b7f96752efe880147439a3a8c2ecc080762f84b5b784da1b41d2ccb1492d112c521b4b82973589285dfbcfbb6c423c559ff4e.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json
new file mode 120000
index 0000000..9bad4bd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json
@@ -0,0 +1 @@
+../../4/repository/765d0de07adc4de8aa6060a9c579c70938f0a6e998d76ad75363945fdb998b8019d4d42a00642141ac8a86346098dfb40bc7be32d684077be5aae06495f57cfa.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json
new file mode 120000
index 0000000..eed6123
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json
@@ -0,0 +1 @@
+../../4/repository/7c7a5e381e1e8c3f10bc4ab1c1d33f6460f741a5c0bbeb8be4f206de58e9ce67964bef7cef0738d22d5c55238d2062a2a9a72c99f6df0c57a2bece6435a3e461.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json
new file mode 120000
index 0000000..9e5a79b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json
@@ -0,0 +1 @@
+../../4/repository/8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/8ec9b98766edfe6acc23bb700183532a990b966ceb7b62784f6c80d4b1bfe5661df3cc2765b57bd0e78eb4b5c2b136f5ab7a365665c6e918d4137b83cb7e2262.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/8ec9b98766edfe6acc23bb700183532a990b966ceb7b62784f6c80d4b1bfe5661df3cc2765b57bd0e78eb4b5c2b136f5ab7a365665c6e918d4137b83cb7e2262.targets.json
new file mode 100644
index 0000000..3f07d3d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/8ec9b98766edfe6acc23bb700183532a990b966ceb7b62784f6c80d4b1bfe5661df3cc2765b57bd0e78eb4b5c2b136f5ab7a365665c6e918d4137b83cb7e2262.targets.json
@@ -0,0 +1,58 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ },
+ "5": {
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ },
+ "length": 1
+ }
+ },
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
new file mode 120000
index 0000000..557a157
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json
@@ -0,0 +1 @@
+../../4/repository/93b38df5b9cb993dd7e2e16e9b8dd07386b35936f21c36b7527e1587052dce86f64b5eb5669fffe86b4aae4c581a2bbba6e49d780d8687dd6c304fae9b6fe098.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json
new file mode 120000
index 0000000..60975e4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json
@@ -0,0 +1 @@
+../../4/repository/bdcbdfe0a1ecaaf9c0c5dc9728bc2eebb1c931b17e57676bd251f8b05640826dcf923ed854c235247329e7068c0d01ad6920b4454a976feb0c9f29b64e338ee4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
new file mode 120000
index 0000000..abbc6bd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json
@@ -0,0 +1 @@
+../../4/repository/bf4f7f4a062fd3780055d98572619db84d6d6c9b9b647bf6bca1190e984963ffb86189fb455110f4c13956ce9cbe72d95d03c5e6d72295414ac6bbcc3d1957c3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json
new file mode 120000
index 0000000..b786bfa
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json
@@ -0,0 +1 @@
+../../4/repository/c86fd75fe946b88488ece0fa399857a75f3cc80bd85bc71f3fcc030ad6201c559370cbc67a8a547f1964355911fc0ef8000dd8e9f0ff84a3cc8e45cbd42ec7db.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json
new file mode 120000
index 0000000..ef1e443
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/dce60b87bbc2b98c1e4cc86cc5798d381561600934b0da7d044ed72511dafefc5e4e640a76e59ea96f6c9edf1a002974180e1562b0496127fcb44ca6b08c294c.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/root.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/root.json
new file mode 120000
index 0000000..3d14058
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/root.json
@@ -0,0 +1 @@
+../../4/repository/root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/snapshot.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/snapshot.json
new file mode 100644
index 0000000..15ab63b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/snapshot.json
@@ -0,0 +1,36 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "8aae80ec8056a31ca28304ed0b0b35f0dacb11c5d6684cc7200086ff237fec23dc1e3fc2b43afb968d780c8053bd4081f4ae7ca71a066894042b1d24983fc0fd"
+ },
+ "length": 3831,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "8ec9b98766edfe6acc23bb700183532a990b966ceb7b62784f6c80d4b1bfe5661df3cc2765b57bd0e78eb4b5c2b136f5ab7a365665c6e918d4137b83cb7e2262"
+ },
+ "length": 1871,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "method": "ed25519",
+ "sig": "61f3a52657285ee76cac838dcf7bd605c8b895dadd7877f5ef3aaa8b22ec0e952fa44c9d9145d5740e56568d5c772c4ca46f96458c813d24c61596cfddff350b"
+ },
+ {
+ "keyid": "ad7c98dfb3b99024f8b49e33f3d92e66537f0abab78a84f1e4b667dfbf52bf60",
+ "method": "ed25519",
+ "sig": "61f3a52657285ee76cac838dcf7bd605c8b895dadd7877f5ef3aaa8b22ec0e952fa44c9d9145d5740e56568d5c772c4ca46f96458c813d24c61596cfddff350b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets.json
new file mode 100644
index 0000000..3f07d3d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets.json
@@ -0,0 +1,58 @@
+{
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ },
+ "5": {
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ },
+ "length": 1
+ }
+ },
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "method": "ed25519",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ },
+ {
+ "keyid": "0efcc6a8d07a1cb5955fb698b8ea57e903b6924854f5a025c90f16c9a52b2700",
+ "method": "ed25519",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.0
new file mode 120000
index 0000000..28473bd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.0
@@ -0,0 +1 @@
+../../../4/repository/targets/0.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.1
new file mode 120000
index 0000000..e371cbf
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.1
@@ -0,0 +1 @@
+../../../4/repository/targets/0.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.2
new file mode 120000
index 0000000..4819cab
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.2
@@ -0,0 +1 @@
+../../../4/repository/targets/0.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.3
new file mode 120000
index 0000000..86d2291
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.3
@@ -0,0 +1 @@
+../../../4/repository/targets/0.3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.4 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.4
new file mode 120000
index 0000000..8ad49cb
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.4
@@ -0,0 +1 @@
+../../../4/repository/targets/0.4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.5 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.5
new file mode 100644
index 0000000..7813681
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/0.5
@@ -0,0 +1 @@
+5 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5
new file mode 100644
index 0000000..7813681
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5
@@ -0,0 +1 @@
+5 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..c70b61b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 120000
index 0000000..1e66cb1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+../../../4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..a02a231
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..74d1328
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
new file mode 120000
index 0000000..73a65ad
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
@@ -0,0 +1 @@
+../../../4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/timestamp.json b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/timestamp.json
new file mode 100644
index 0000000..fb03158
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/consistent-snapshot-true/5/repository/timestamp.json
@@ -0,0 +1,29 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "63c16cc2225e77b7c94ec3b143271e02d97a1bfba828e11820392b9108412ef22f09732694673331f386b6f8a281103e20d07c21db78f487e1b046d9968173c8"
+ },
+ "length": 1126,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ },
+ "signatures": [
+ {
+ "keyid": "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "method": "ed25519",
+ "sig": "93de7e13e6ad7b03be3c299169aba2cf1fed7a6bac8b3059ca858e4a709063b6a0237b78614e9101c9beed0d9f470bfcd77644108246688d75cf58b243ea1f0c"
+ },
+ {
+ "keyid": "e23c19fb64db7146965700b9983df63d2dd4362b1f99d74da2383234da4ab20d",
+ "method": "ed25519",
+ "sig": "93de7e13e6ad7b03be3c299169aba2cf1fed7a6bac8b3059ca858e4a709063b6a0237b78614e9101c9beed0d9f470bfcd77644108246688d75cf58b243ea1f0c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M3/generate.go b/client/testdata/go-tuf-transition-M3/generate.go
new file mode 100644
index 0000000..2a9d20f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M3/generate.go
@@ -0,0 +1,150 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "time"
+
+ tuf "github.com/theupdateframework/go-tuf"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+var expirationDate = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
+
+type persistedKeys struct {
+ Encrypted bool `json:"encrypted"`
+ Data []*data.PrivateKey `json:"data"`
+}
+
+func assertNoError(err error) {
+ if err != nil {
+ panic(fmt.Sprintf("assertion failed: %s", err))
+ }
+}
+
+func copyRepo(src string, dst string) {
+ cmd := exec.Command("cp", "-r", src, dst)
+ assertNoError(cmd.Run())
+}
+
+func newRepo(dir string) *tuf.Repo {
+ repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
+ assertNoError(err)
+
+ return repo
+}
+
+func commit(dir string, repo *tuf.Repo) {
+ 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.
+ 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)
+ assertNoError(err)
+ assertNoError(repo.AddPrivateKeyWithExpires(role, signer, expirationDate))
+ }
+ }
+}
+
+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)
+ assertNoError(os.MkdirAll(filepath.Dir(path), 0755))
+ assertNoError(os.WriteFile(path, data, 0644))
+ paths = append(paths, file)
+ }
+ 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)
+ assertNoError(err)
+ assertNoError(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
+ }
+}
+
+func generateRepos(dir string, consistentSnapshot bool) {
+ f, err := os.Open("../keys.json")
+ assertNoError(err)
+
+ var roleKeys map[string][][]*data.PrivateKey
+ 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.
+ keys := map[string][]*data.PrivateKey{
+ "root": roleKeys["root"][0],
+ "targets": roleKeys["targets"][0],
+ "snapshot": roleKeys["snapshot"][0],
+ "timestamp": roleKeys["timestamp"][0],
+ }
+
+ // Create the initial repo.
+ dir0 := filepath.Join(dir, "0")
+ repo0 := newRepo(dir0)
+ repo0.Init(consistentSnapshot)
+ addKeys(repo0, keys)
+ addTargets(repo0, dir0, map[string][]byte{"0": []byte("0")})
+ commit(dir0, repo0)
+
+ // Rotate all the keys to make sure that works.
+ oldDir := dir0
+ i := 1
+ for _, role := range []string{"root", "targets", "snapshot", "timestamp"} {
+ // Setup the repo.
+ stepName := fmt.Sprintf("%d", i)
+ d := filepath.Join(dir, stepName)
+ copyRepo(oldDir, d)
+ repo := newRepo(d)
+ addKeys(repo, keys)
+
+ // Actually rotate the keys
+ revokeKeys(repo, role, roleKeys[role][0])
+ addKeys(repo, map[string][]*data.PrivateKey{
+ role: roleKeys[role][1],
+ })
+ keys[role] = roleKeys[role][1]
+
+ // Add a target to make sure that works, then commit.
+ addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
+ commit(d, repo)
+
+ i += 1
+ oldDir = d
+ }
+
+ // Add another target file to make sure the workflow worked.
+ stepName := fmt.Sprintf("%d", i)
+ d := filepath.Join(dir, stepName)
+ copyRepo(oldDir, d)
+ repo := newRepo(d)
+ addKeys(repo, keys)
+ addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
+ commit(d, repo)
+}
+
+func main() {
+ cwd, err := os.Getwd()
+ assertNoError(err)
+
+ for _, consistentSnapshot := range []bool{false, true} {
+ name := fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot)
+ log.Printf("generating %s", name)
+ generateRepos(filepath.Join(cwd, name), consistentSnapshot)
+ }
+
+}
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/1.root.json
new file mode 100644
index 0000000..f02c25b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/1.root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "sig": "4d84952574a5ef31fa9f8d97a3159305b8b99c011b27327e969d88f8f88a5d56ef4a3d03b19134d0f8a1627de8d03b11847ffb7ed0e15c2fad0d8cfd5a57ae0f"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/root.json
new file mode 100644
index 0000000..f02c25b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "sig": "4d84952574a5ef31fa9f8d97a3159305b8b99c011b27327e969d88f8f88a5d56ef4a3d03b19134d0f8a1627de8d03b11847ffb7ed0e15c2fad0d8cfd5a57ae0f"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/snapshot.json
new file mode 100644
index 0000000..a846dde
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "0c2eb81db397d04faed82a5b75c4e5980d3fb2dafb13ff6892a7bdc131a6d698fe635ee369a016a4a8e47964a77f56f834cc432853d76feccc70e3fbb9dcc70a"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "d2a63252478ef0e4baef238f277261b469dac193d731c618194d37492dcd57eb391563fe065c2df054628dc28ae9896f20670a41b1264507a739ff36dab85d9f"
+ },
+ "length": 2118,
+ "version": 1
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "257ae8c4653e9ecd1580d0f95ece3cbd03d67ace62e670c0493cde2afde96670a1620fcdf4a2e81ecbf8a67c7b0af9e03e6f9c5c8195d4b1940f7e0e61cc04c8"
+ },
+ "length": 588,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/targets.json
new file mode 100644
index 0000000..7e9ac2d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/targets.json
@@ -0,0 +1,22 @@
+{
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/targets/0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/targets/0
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/targets/0
@@ -0,0 +1 @@
+0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/timestamp.json
new file mode 100644
index 0000000..f03cdb6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/0/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "sig": "cff803896a6af526281ad8ae7e3c3039bd850a4f56afc23e50564b9259719d2dd9e2f7956bf89f04d405a6f2c836db0ae98fa21846921bba39ed43adbf728c0e"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "9f96c109f0d6a53fa01567d1577fdbc02a34c4a755cba6cfb2e3dd9f6c87596755b9285317c6640a139abad25dcbac22da79208f37ecffd17c67fb87fc89ce1c"
+ },
+ "length": 847,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/1.root.json
new file mode 120000
index 0000000..0ffd2b6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/1.root.json
@@ -0,0 +1 @@
+../../0/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/2.root.json
new file mode 100644
index 0000000..7c2571f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/2.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "sig": "2c7952346c8f899b15f33048bfc22db078a2ee5df5ae4dfc1c7e4eb303ee5dd99b6957d8bddd39ddddbc2e49b0abd76e27c0b0439984ae36e23db5bafb51d104"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "a9ae363c57c79235e0fb646f364a747e88d61a25eef9e92e44f13f6849bb62ee1a91db6ba021cbef330e9a5b3e2131a45b5922bbd1a30596a11973743aedc404"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/root.json
new file mode 100644
index 0000000..7c2571f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "sig": "2c7952346c8f899b15f33048bfc22db078a2ee5df5ae4dfc1c7e4eb303ee5dd99b6957d8bddd39ddddbc2e49b0abd76e27c0b0439984ae36e23db5bafb51d104"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "a9ae363c57c79235e0fb646f364a747e88d61a25eef9e92e44f13f6849bb62ee1a91db6ba021cbef330e9a5b3e2131a45b5922bbd1a30596a11973743aedc404"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/snapshot.json
new file mode 100644
index 0000000..aee62e6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "3b170ced5f99155d617d933721eb8b1bc37d53bacd13787495f5b65f8e217898b0432391ddc76ddf350d367a590f39c44ef89350c67119d7c3cf869375da6004"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "fac18f6f1804305c960c48700111947a77b6249659a43ebc1caffb701784a16a3967bb640b7ad612235f6b5078b9c9f178b0cb5acaf9d2a49cabd4bd8ec418de"
+ },
+ "length": 2348,
+ "version": 2
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "0fa0d9f7f64ffaf2a52bda808851c71ae87c7c47aaec4ce2ba28c19123a0d4394156dce76012a5253c1c02ccdf88843cdb980042eab746e1a93ce2541f89d91c"
+ },
+ "length": 789,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets.json
new file mode 100644
index 0000000..eb11538
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ }
+ },
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets/0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets/0
new file mode 120000
index 0000000..aced687
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets/0
@@ -0,0 +1 @@
+../../../0/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets/1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets/1
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/targets/1
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/timestamp.json
new file mode 100644
index 0000000..e359652
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/1/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "sig": "df50c702b51dec901cd5d6f548b4c551ab578aaf8e791d7d3fc85697f291e6102c64e9d758fb4291d09dd28bcfd442cdb7598167821b7b50247b4af3f763780c"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "c150afd2864d6b6f0f14e4549c592418abf4b390ca6c88f0a50c4e615f54cc449cd9cf9dfbc227bea900fd3007921fb64ff6fd30bf5cfa2877682af66368a2f6"
+ },
+ "length": 847,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/1.root.json
new file mode 120000
index 0000000..cd73051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/1.root.json
@@ -0,0 +1 @@
+../../1/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/2.root.json
new file mode 120000
index 0000000..bbdcb12
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/2.root.json
@@ -0,0 +1 @@
+../../1/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/3.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/3.root.json
new file mode 100644
index 0000000..923eafc
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/3.root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "ea2529b895eeabd2aaf00c06d5789fc3152aab134d8f1265954304c89f4a6eeca4e7f0be6620c8fce14caf4c447b8c472483438d74273d2a50ea86f5de59aa06"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/root.json
new file mode 100644
index 0000000..923eafc
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "ea2529b895eeabd2aaf00c06d5789fc3152aab134d8f1265954304c89f4a6eeca4e7f0be6620c8fce14caf4c447b8c472483438d74273d2a50ea86f5de59aa06"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/snapshot.json
new file mode 100644
index 0000000..f68be59
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "e4a00bb62fb3c1efd1b949050d952d89023461ab6db280f2b5233bafd8b672abfc23b22453eb1becb5d850eb70c7e201ee6780555fabd5f6c4d68b69c82f1406"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "cd1af4dae35649774a2090ea0471ccaf677dddd080973807faa37c0744649f316052603c43032fc308c85c60379bf5067201957622061ad91acd942a1b55b274"
+ },
+ "length": 2118,
+ "version": 3
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "33835f152dcc9ab1396e0f44dff841eca2c747dfd8cccc079dd0f4a88e7920f29cfaa3d895618c39463bed3997d6ba83c7791749483f2df8f3589fff3f4c379d"
+ },
+ "length": 990,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets.json
new file mode 100644
index 0000000..6c7e919
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets.json
@@ -0,0 +1,34 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ }
+ },
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/0
new file mode 120000
index 0000000..2ac1d8b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/0
@@ -0,0 +1 @@
+../../../1/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/1
new file mode 120000
index 0000000..8e78bc3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/1
@@ -0,0 +1 @@
+../../../1/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/2 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/2
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/targets/2
@@ -0,0 +1 @@
+2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/timestamp.json
new file mode 100644
index 0000000..55ef71f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/2/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "sig": "3c11c4fea59b6433d678dfa7e6405182d7337f66bfcbe74c82602c689f2bfcca8cb5663ddf8d325fcc3e434c50bea08777e07d34ff439264f1e7cb688921ef00"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "0ab33430ecbe903acaafa3158ad1bfe2cfc22968b5966a6816c16d16a72bfa5da09426d98b547fc5c8784a6502972977c331956ed84c525f1e85fb385a36fb58"
+ },
+ "length": 847,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/1.root.json
new file mode 120000
index 0000000..fb4fe81
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/1.root.json
@@ -0,0 +1 @@
+../../2/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/2.root.json
new file mode 120000
index 0000000..55f8b9d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/2.root.json
@@ -0,0 +1 @@
+../../2/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/3.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/3.root.json
new file mode 120000
index 0000000..b4564b7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/3.root.json
@@ -0,0 +1 @@
+../../2/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/4.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/4.root.json
new file mode 100644
index 0000000..c71e3b7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/4.root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "ec7e22d27d5b5c43d350ae7b6345fd0acc37b7106063081f637f49fe09cc8f4c6db664f2198d53f930dc061c1ab25d1b015336a12085ee582e823eaf309b1202"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/root.json
new file mode 100644
index 0000000..c71e3b7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "ec7e22d27d5b5c43d350ae7b6345fd0acc37b7106063081f637f49fe09cc8f4c6db664f2198d53f930dc061c1ab25d1b015336a12085ee582e823eaf309b1202"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/snapshot.json
new file mode 100644
index 0000000..e67fb80
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "fb791108edb246566da93f43af287fc8bb7ac03d6a7c615b4c14622a95a9f029fe1b1ad97c14554bc5fa681d39df2b67858a3b63e5d49578c87f1a1a134bdb0e"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "52f836fc2bdeec872c621b84a87a1ab9e2d46763d640bbb775f862306b060ba51370ff838b9589a732f86938509c3a55731d2e3e6f1601d14c42c47efe990ddc"
+ },
+ "length": 2118,
+ "version": 4
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "518518c1f03d874f9832390dea5bc12ab0061400bd0b3b8b44e4a353087d802accde1ec3dc94e0be4931f572960a0192bd84e667c02e6ac99d0a3bf28f5f9802"
+ },
+ "length": 1191,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets.json
new file mode 100644
index 0000000..4ed9d72
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets.json
@@ -0,0 +1,40 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ }
+ },
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/0
new file mode 120000
index 0000000..2e6dba0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/0
@@ -0,0 +1 @@
+../../../2/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/1
new file mode 120000
index 0000000..14fee5d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/1
@@ -0,0 +1 @@
+../../../2/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/2 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/2
new file mode 120000
index 0000000..dcd19e0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/2
@@ -0,0 +1 @@
+../../../2/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/3 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/3
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/targets/3
@@ -0,0 +1 @@
+3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/timestamp.json
new file mode 100644
index 0000000..d6a3549
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/3/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "sig": "f93295631c148c1f4949c44cb07e1daca4b0cf587e5d3b999740b3e012e0dd2780803b3aec44c9fa1f27d0f5fa28136073331c41d81de5e4b82a7d7df5591509"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "6455d62f5676402784607019dbb9a23ab31ee61b47a67a4fcd2a69bf3a3b5ec60b602b42112626f17ef2ec0177c4392e61d587637c06f636831ff63c3317827d"
+ },
+ "length": 848,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/1.root.json
new file mode 120000
index 0000000..741d218
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/1.root.json
@@ -0,0 +1 @@
+../../3/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/2.root.json
new file mode 120000
index 0000000..fb9b350
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/2.root.json
@@ -0,0 +1 @@
+../../3/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/3.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/3.root.json
new file mode 120000
index 0000000..65e6c5b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/3.root.json
@@ -0,0 +1 @@
+../../3/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/4.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/4.root.json
new file mode 120000
index 0000000..c3662ce
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/4.root.json
@@ -0,0 +1 @@
+../../3/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/5.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/5.root.json
new file mode 100644
index 0000000..d112eae
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/5.root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "fc0f92883c9c89135505c1ae59ec11bf4bbd82ead0dca1d719eda4251345830514f0bd1b264d36d26b971ab58d7ee3f10110190cadee753b24fc98eaa61d2004"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/root.json
new file mode 100644
index 0000000..d112eae
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "fc0f92883c9c89135505c1ae59ec11bf4bbd82ead0dca1d719eda4251345830514f0bd1b264d36d26b971ab58d7ee3f10110190cadee753b24fc98eaa61d2004"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/snapshot.json
new file mode 100644
index 0000000..94ed0e9
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "074a04f426d423f2675b58c8db38814bfe1bded4717c6c748ff2484cbc94fe2b67cb166cfe4550908565759018427042ebc59fc95005c105d12c1c0c1d779808"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "6393ac77695995ad751bf33c885882088a4f5c3b1c58ed75433007a5c79aef45aeea10881094686254198e12fc78be781f777947f23ed5bef0bdb157ef55e74d"
+ },
+ "length": 2118,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "25ba8ef109680040b2e28dde51389397c51f4ba12acb1ea1b70dfc7e1bc5eceec71b1cbfb4747c47af8d9cdf76baa53ab5b2403eef226be1ae7a8d7794474ebf"
+ },
+ "length": 1392,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets.json
new file mode 100644
index 0000000..163e9e8
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets.json
@@ -0,0 +1,46 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ }
+ },
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/0
new file mode 120000
index 0000000..efe18e4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/0
@@ -0,0 +1 @@
+../../../3/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/1
new file mode 120000
index 0000000..b4a40c4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/1
@@ -0,0 +1 @@
+../../../3/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/2 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/2
new file mode 120000
index 0000000..1c68171
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/2
@@ -0,0 +1 @@
+../../../3/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/3 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/3
new file mode 120000
index 0000000..1dc4d27
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/3
@@ -0,0 +1 @@
+../../../3/repository/targets/3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/4 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/4
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/targets/4
@@ -0,0 +1 @@
+4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/timestamp.json
new file mode 100644
index 0000000..f028ca8
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/4/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "sig": "5f5f3f30efa3531c4a8174283820a7c499b15c36f93e5f0e179b32be898e4a43810cc46e3bd06cda75b16237a258f909884ae3718189830f6e2991b5d4968b04"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "26e8fa7db5884e5b8c6868898d8ad2fa26d37da07255364b48d4e09560e83b25586767ee53e2c39eb372d6ef0db0c8d7ae72a50bfb0bdab57510439d5e3ceef8"
+ },
+ "length": 848,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/1.root.json
new file mode 120000
index 0000000..fb796e3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/1.root.json
@@ -0,0 +1 @@
+../../4/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/2.root.json
new file mode 120000
index 0000000..5a3e454
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/2.root.json
@@ -0,0 +1 @@
+../../4/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/3.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/3.root.json
new file mode 120000
index 0000000..d1cb251
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/3.root.json
@@ -0,0 +1 @@
+../../4/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/4.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/4.root.json
new file mode 120000
index 0000000..bda6d08
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/4.root.json
@@ -0,0 +1 @@
+../../4/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/5.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/5.root.json
new file mode 120000
index 0000000..f99552a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/5.root.json
@@ -0,0 +1 @@
+../../4/repository/5.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/root.json
new file mode 120000
index 0000000..3d14058
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/root.json
@@ -0,0 +1 @@
+../../4/repository/root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/snapshot.json
new file mode 100644
index 0000000..4c3731e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "a44ce7ca76f178c41acf92e6cb6cacc5c4cd17e1e99cc0d7ba8fb37d11b468b8475ad2801adeef7a246947595f845545be420ee912bfcb7091708530fab3eb01"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "6393ac77695995ad751bf33c885882088a4f5c3b1c58ed75433007a5c79aef45aeea10881094686254198e12fc78be781f777947f23ed5bef0bdb157ef55e74d"
+ },
+ "length": 2118,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "c57a6fd231341bea3adb36d5a9c07167ef26bf9b037470bef1dadfeb2cdafa73b39b30965e39175fa9be5426065073bbf859f149c366ae07ea2a0f6fb6730b00"
+ },
+ "length": 1593,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets.json
new file mode 100644
index 0000000..c528a43
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets.json
@@ -0,0 +1,52 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ },
+ "5": {
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ },
+ "length": 1
+ }
+ },
+ "version": 6
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/0
new file mode 120000
index 0000000..94cc287
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/0
@@ -0,0 +1 @@
+../../../4/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/1
new file mode 120000
index 0000000..51c7c9a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/1
@@ -0,0 +1 @@
+../../../4/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/2 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/2
new file mode 120000
index 0000000..46e3032
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/2
@@ -0,0 +1 @@
+../../../4/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/3 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/3
new file mode 120000
index 0000000..ca268e9
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/3
@@ -0,0 +1 @@
+../../../4/repository/targets/3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/4 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/4
new file mode 120000
index 0000000..19339ad
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/4
@@ -0,0 +1 @@
+../../../4/repository/targets/4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/5 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/5
new file mode 100644
index 0000000..7813681
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/targets/5
@@ -0,0 +1 @@
+5 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/timestamp.json
new file mode 100644
index 0000000..4a6a11f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-false/5/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "sig": "5de52850930dc5e8e6c5c1d85d84e8d968f170904c958de30473f81f8293e7b48e27e9d2afa1592eb1d527a0937553ee5e2540f0255837d2f93c5af12d510f08"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "c0fb132da6b90f31ba8c1122388c13d37e4a553ff5b5fa0a65dcf19c53af41965a62b7f88526b4363b288005bfc286c4480fa6d8d63ba54a0ec491f14329b549"
+ },
+ "length": 848,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.root.json
new file mode 100644
index 0000000..53cd546
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "sig": "971ed150895f0dc14d5a2cfb7d805131ba7d0152f315f1e8498f5a0119fa0254c4f4dd4cdbb6401e8d2e1649df1dd91ed56cf5623986a81135b5e94f40dbda08"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.snapshot.json
new file mode 100644
index 0000000..38848bd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "baefc778a9aaa93cfa86bd9d0f5b4d05c58c48e1abdff6d44f1c0b3ffac7c18989c357f78222f4decc52f0372251679ca074b3969e11991a424c6c2f3bda9709"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "c125be40b37191d913a727873ff99224f2641d665534211641c7bf8bc7813e3b9b90a80380d118c5815188f799ba8e43d3ccc7dcb2ea42563e7250ac7791c1db"
+ },
+ "length": 2117,
+ "version": 1
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "257ae8c4653e9ecd1580d0f95ece3cbd03d67ace62e670c0493cde2afde96670a1620fcdf4a2e81ecbf8a67c7b0af9e03e6f9c5c8195d4b1940f7e0e61cc04c8"
+ },
+ "length": 588,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.targets.json
new file mode 100644
index 0000000..7e9ac2d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/1.targets.json
@@ -0,0 +1,22 @@
+{
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/root.json
new file mode 100644
index 0000000..53cd546
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "sig": "971ed150895f0dc14d5a2cfb7d805131ba7d0152f315f1e8498f5a0119fa0254c4f4dd4cdbb6401e8d2e1649df1dd91ed56cf5623986a81135b5e94f40dbda08"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/snapshot.json
new file mode 100644
index 0000000..38848bd
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "baefc778a9aaa93cfa86bd9d0f5b4d05c58c48e1abdff6d44f1c0b3ffac7c18989c357f78222f4decc52f0372251679ca074b3969e11991a424c6c2f3bda9709"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "c125be40b37191d913a727873ff99224f2641d665534211641c7bf8bc7813e3b9b90a80380d118c5815188f799ba8e43d3ccc7dcb2ea42563e7250ac7791c1db"
+ },
+ "length": 2117,
+ "version": 1
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "257ae8c4653e9ecd1580d0f95ece3cbd03d67ace62e670c0493cde2afde96670a1620fcdf4a2e81ecbf8a67c7b0af9e03e6f9c5c8195d4b1940f7e0e61cc04c8"
+ },
+ "length": 588,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/targets.json
new file mode 100644
index 0000000..7e9ac2d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/targets.json
@@ -0,0 +1,22 @@
+{
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/timestamp.json
new file mode 100644
index 0000000..93fb20b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/0/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "sig": "d66dc8ec2ede5c442ec600c22c46a5e0661bfdaae9a24ace66fedafe6a8f7a8d20cbe8685d3e0947c5d84bc4e9fdd4f4a442477c1544316678eef537fcdd0e06"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "67b27e18e00391e67d5ce85d60cc8aaf862cff0d22e541d044c39373ed7d524969ab8a31a826722a94ee42ff1e81eedcefba379b2f02f0116b4c30c5285692f3"
+ },
+ "length": 847,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.root.json
new file mode 120000
index 0000000..0ffd2b6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.root.json
@@ -0,0 +1 @@
+../../0/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.snapshot.json
new file mode 120000
index 0000000..840f843
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../0/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.targets.json
new file mode 120000
index 0000000..4d11148
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/1.targets.json
@@ -0,0 +1 @@
+../../0/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.root.json
new file mode 100644
index 0000000..b0d7ec3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "sig": "953fe3874eeb876b137ff4449d6e3d49d61a0b9e7bd339a47f9df3bd19e197aeee1e6b2b89a0ed4425c66e551b396ec53ea73f46d772ca81e27a3daf917e130d"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "09cd32bfed745ebdd6c8f69c3dca6ac74ddaf0121098442414ef4cb141f4916b14cc2127223f9ffaec2a4e0bde03c04f33aba3c357f7f8c3544666bfe84ede02"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.snapshot.json
new file mode 100644
index 0000000..592682f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "b0298a786de3c14313b5a8f5afe8bbab0a407699a91062b8ead172f3afc74fc27210a7a042978c7d42b4ae9874f4bb139628be8a7be5b57c7bacc9f5a4cec809"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "df686bed8fac62391d47356f6be52fe3316bf856a415a7a2102369289d23d274a739a1071f6095a712654e6b75296b726198e67604666d808c2e7caa3964f419"
+ },
+ "length": 2347,
+ "version": 2
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "0fa0d9f7f64ffaf2a52bda808851c71ae87c7c47aaec4ce2ba28c19123a0d4394156dce76012a5253c1c02ccdf88843cdb980042eab746e1a93ce2541f89d91c"
+ },
+ "length": 789,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.targets.json
new file mode 100644
index 0000000..eb11538
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/2.targets.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ }
+ },
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/root.json
new file mode 100644
index 0000000..b0d7ec3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "160f5ae5e7017bc783bbbe26c5419bbeeeb87f507b140ef474da0517d56549f7",
+ "sig": "953fe3874eeb876b137ff4449d6e3d49d61a0b9e7bd339a47f9df3bd19e197aeee1e6b2b89a0ed4425c66e551b396ec53ea73f46d772ca81e27a3daf917e130d"
+ },
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "09cd32bfed745ebdd6c8f69c3dca6ac74ddaf0121098442414ef4cb141f4916b14cc2127223f9ffaec2a4e0bde03c04f33aba3c357f7f8c3544666bfe84ede02"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/snapshot.json
new file mode 100644
index 0000000..592682f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "b0298a786de3c14313b5a8f5afe8bbab0a407699a91062b8ead172f3afc74fc27210a7a042978c7d42b4ae9874f4bb139628be8a7be5b57c7bacc9f5a4cec809"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "df686bed8fac62391d47356f6be52fe3316bf856a415a7a2102369289d23d274a739a1071f6095a712654e6b75296b726198e67604666d808c2e7caa3964f419"
+ },
+ "length": 2347,
+ "version": 2
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "0fa0d9f7f64ffaf2a52bda808851c71ae87c7c47aaec4ce2ba28c19123a0d4394156dce76012a5253c1c02ccdf88843cdb980042eab746e1a93ce2541f89d91c"
+ },
+ "length": 789,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets.json
new file mode 100644
index 0000000..eb11538
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "b1eb1d74c0e65524c85f96f9badf09a4d2ed1259fb427fc689fc2279ebb4d867",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ }
+ },
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..b0c459f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/timestamp.json
new file mode 100644
index 0000000..96de50e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/1/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "sig": "d55dc9bd3e7a5a56cada46ca40858b7dd48fd994de1ea0067e20e9fc6401faf5d25b20ddc8f93a966d82d4f0eabf7ef03c3612dd01339bb7527a5271b1c41b07"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "41a2de22c5a4b972102e2c65b9f5024c384be52a03c345a36736c3bee79e30a7aa3853c4dfdc7dfc3d5ddb713258454f0ef9140ac52e832f3ca927030013ca8e"
+ },
+ "length": 847,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.root.json
new file mode 120000
index 0000000..cd73051
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.root.json
@@ -0,0 +1 @@
+../../1/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.snapshot.json
new file mode 120000
index 0000000..1307f57
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../1/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.targets.json
new file mode 120000
index 0000000..85e1ecc
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/1.targets.json
@@ -0,0 +1 @@
+../../1/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.root.json
new file mode 120000
index 0000000..bbdcb12
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.root.json
@@ -0,0 +1 @@
+../../1/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.snapshot.json
new file mode 120000
index 0000000..24d1075
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../1/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.targets.json
new file mode 120000
index 0000000..f6a1749
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/2.targets.json
@@ -0,0 +1 @@
+../../1/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.root.json
new file mode 100644
index 0000000..8ac5eb0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "46ef3256d2df1f7259ec3044030cc6af71536aee4976b8858a46b0f0e98763679794546bcc21736231c0dac8cfbad936bdd18437d02a1c97e7ef347c16ddeb0c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.snapshot.json
new file mode 100644
index 0000000..7363452
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "42974be1e70b41809ab6c5bfaaef53508c3c1184cd045ee38cd737b0c9debaddbc0244731cbd208ead966561ab1103bae40d26fa3236ac974cb589ad77019c0e"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "c113580e6b210d8cdaf93530870555cac34cd7a394a91ffd0462ed38326766f09c5712b1fb1c3ad53e7b2c24feac0180649b4fdcea7722e5c1ab05635709b4d4"
+ },
+ "length": 2117,
+ "version": 3
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "33835f152dcc9ab1396e0f44dff841eca2c747dfd8cccc079dd0f4a88e7920f29cfaa3d895618c39463bed3997d6ba83c7791749483f2df8f3589fff3f4c379d"
+ },
+ "length": 990,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.targets.json
new file mode 100644
index 0000000..6c7e919
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/3.targets.json
@@ -0,0 +1,34 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ }
+ },
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/root.json
new file mode 100644
index 0000000..8ac5eb0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "46ef3256d2df1f7259ec3044030cc6af71536aee4976b8858a46b0f0e98763679794546bcc21736231c0dac8cfbad936bdd18437d02a1c97e7ef347c16ddeb0c"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/snapshot.json
new file mode 100644
index 0000000..7363452
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "d792ff8cf3c5d9a8fe0fdfa585b25028fa9f49aa29c62a658140d4b98cc2c917",
+ "sig": "42974be1e70b41809ab6c5bfaaef53508c3c1184cd045ee38cd737b0c9debaddbc0244731cbd208ead966561ab1103bae40d26fa3236ac974cb589ad77019c0e"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "c113580e6b210d8cdaf93530870555cac34cd7a394a91ffd0462ed38326766f09c5712b1fb1c3ad53e7b2c24feac0180649b4fdcea7722e5c1ab05635709b4d4"
+ },
+ "length": 2117,
+ "version": 3
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "33835f152dcc9ab1396e0f44dff841eca2c747dfd8cccc079dd0f4a88e7920f29cfaa3d895618c39463bed3997d6ba83c7791749483f2df8f3589fff3f4c379d"
+ },
+ "length": 990,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets.json
new file mode 100644
index 0000000..6c7e919
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets.json
@@ -0,0 +1,34 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ }
+ },
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..de9aac3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..2ebb2d3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/timestamp.json
new file mode 100644
index 0000000..8107146
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/2/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "sig": "6344ddf5432d68616adb91cb81ad5017d17d95c238f15e4a5fe291e145d19d30b4834917a233c4c897ede7ba2d61dd23cdb285134de2b09b84ab62e8fea0120a"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "78828a5b8f961563240c4037cb8c90778ec5b14c54fbd2fc4e3c9ace67c55061d23cb532713ce23bfa71c1699f33dba928046749c1a70b0bf83688df12850353"
+ },
+ "length": 847,
+ "version": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.root.json
new file mode 120000
index 0000000..fb4fe81
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.root.json
@@ -0,0 +1 @@
+../../2/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.snapshot.json
new file mode 120000
index 0000000..4dfb0d0
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.targets.json
new file mode 120000
index 0000000..1eeb564
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/1.targets.json
@@ -0,0 +1 @@
+../../2/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.root.json
new file mode 120000
index 0000000..55f8b9d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.root.json
@@ -0,0 +1 @@
+../../2/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.snapshot.json
new file mode 120000
index 0000000..65445cb
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.targets.json
new file mode 120000
index 0000000..3600a0f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/2.targets.json
@@ -0,0 +1 @@
+../../2/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.root.json
new file mode 120000
index 0000000..b4564b7
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.root.json
@@ -0,0 +1 @@
+../../2/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.snapshot.json
new file mode 120000
index 0000000..7d86b95
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.targets.json
new file mode 120000
index 0000000..d1df10e
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/3.targets.json
@@ -0,0 +1 @@
+../../2/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.root.json
new file mode 100644
index 0000000..a889f17
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "78594f8ff3160bfef32c85e58a795d8dc6831e4ec2596e6a93d17f2bcb183c3c2e2ec99a28cf9467ab9a947c5803f3c16703f9fada8b43f33ccd3c49bf8fe707"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.snapshot.json
new file mode 100644
index 0000000..dce624d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "c186db540040a5f552e34365f62a497164ffe1a657bfd30ff5d7fa9eec7f1cf0a4302f99a28233b64492d27bd5a85f7a6114a7dba875f087f7ea27d553d0a80e"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "1ab59b54e351d71820aff7a1a8c845e1d6820793ab542e798989bd28d2db80a05f1382dd4b403f387793be5bacbc179be429ee13e8ff5cb6a4b7d6e73b3e30bc"
+ },
+ "length": 2117,
+ "version": 4
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "518518c1f03d874f9832390dea5bc12ab0061400bd0b3b8b44e4a353087d802accde1ec3dc94e0be4931f572960a0192bd84e667c02e6ac99d0a3bf28f5f9802"
+ },
+ "length": 1191,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.targets.json
new file mode 100644
index 0000000..4ed9d72
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/4.targets.json
@@ -0,0 +1,40 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ }
+ },
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/root.json
new file mode 100644
index 0000000..a889f17
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "78594f8ff3160bfef32c85e58a795d8dc6831e4ec2596e6a93d17f2bcb183c3c2e2ec99a28cf9467ab9a947c5803f3c16703f9fada8b43f33ccd3c49bf8fe707"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/snapshot.json
new file mode 100644
index 0000000..dce624d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "c186db540040a5f552e34365f62a497164ffe1a657bfd30ff5d7fa9eec7f1cf0a4302f99a28233b64492d27bd5a85f7a6114a7dba875f087f7ea27d553d0a80e"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "1ab59b54e351d71820aff7a1a8c845e1d6820793ab542e798989bd28d2db80a05f1382dd4b403f387793be5bacbc179be429ee13e8ff5cb6a4b7d6e73b3e30bc"
+ },
+ "length": 2117,
+ "version": 4
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "518518c1f03d874f9832390dea5bc12ab0061400bd0b3b8b44e4a353087d802accde1ec3dc94e0be4931f572960a0192bd84e667c02e6ac99d0a3bf28f5f9802"
+ },
+ "length": 1191,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets.json
new file mode 100644
index 0000000..4ed9d72
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets.json
@@ -0,0 +1,40 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ }
+ },
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..f820397
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..7cbcc2a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..180cdb4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/timestamp.json
new file mode 100644
index 0000000..6df0aaf
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/3/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "b59406a55fbe2492022f2951dacd01fc7b400b7b41ec4028b8356161a5a71a33",
+ "sig": "d854e4d42a126ba8b7d643c5f8d721d9e3bef4e950a8c61f521148888dbda1f7f7ac6f665029a15d1c1e52787c3677611be4a1cb7251e93a3fbc0229f6e5a40f"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "9af21a02c5b066aea02f0216fc22cb66e40c95d0bcbd50d35f16e32636beb0ffd41f1547ec86e2d0640322953d3ffb8285bb495065898cd8b95faf54fd5a3309"
+ },
+ "length": 848,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.root.json
new file mode 120000
index 0000000..741d218
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.root.json
@@ -0,0 +1 @@
+../../3/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.snapshot.json
new file mode 120000
index 0000000..f87f7ed
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.targets.json
new file mode 120000
index 0000000..b0002ec
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/1.targets.json
@@ -0,0 +1 @@
+../../3/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.root.json
new file mode 120000
index 0000000..fb9b350
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.root.json
@@ -0,0 +1 @@
+../../3/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.snapshot.json
new file mode 120000
index 0000000..e96983b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.targets.json
new file mode 120000
index 0000000..dbb2a75
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/2.targets.json
@@ -0,0 +1 @@
+../../3/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.root.json
new file mode 120000
index 0000000..65e6c5b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.root.json
@@ -0,0 +1 @@
+../../3/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.snapshot.json
new file mode 120000
index 0000000..f2fd560
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.targets.json
new file mode 120000
index 0000000..ca61e77
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/3.targets.json
@@ -0,0 +1 @@
+../../3/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.root.json
new file mode 120000
index 0000000..c3662ce
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.root.json
@@ -0,0 +1 @@
+../../3/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.snapshot.json
new file mode 120000
index 0000000..a2f839a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.targets.json
new file mode 120000
index 0000000..9f7a784
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/4.targets.json
@@ -0,0 +1 @@
+../../3/repository/4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.root.json
new file mode 100644
index 0000000..4e4912d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "a8bf62746b62a58f80c81bdbbdc67d142f1db9e6681ac0e46bf7d33aeac527e86af8294a35fea4cfd6c750d4f1333287c201837b2b37fce7a440ea58bdb0d907"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.snapshot.json
new file mode 100644
index 0000000..e62aef9
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "6c6b90af042c63f79a7d8079a26bd58fdcb8d84c829fd62aa554b4e9df6d31dfb5a6efb7744b814f7536ace6196c2e0b2768794fa40abf85b49dcc661d0f9a06"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "7a60ff2c4e027e05c318e9a0dc891c46e9899994b50c2ac0dad0bd6f439c69a71486606adf309969f981de6b20630e27f0e529e020c41c638b9f0895ccbd797b"
+ },
+ "length": 2117,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "25ba8ef109680040b2e28dde51389397c51f4ba12acb1ea1b70dfc7e1bc5eceec71b1cbfb4747c47af8d9cdf76baa53ab5b2403eef226be1ae7a8d7794474ebf"
+ },
+ "length": 1392,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.targets.json
new file mode 100644
index 0000000..163e9e8
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/5.targets.json
@@ -0,0 +1,46 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ }
+ },
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/root.json
new file mode 100644
index 0000000..4e4912d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/root.json
@@ -0,0 +1,83 @@
+{
+ "signatures": [
+ {
+ "keyid": "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d",
+ "sig": "a8bf62746b62a58f80c81bdbbdc67d142f1db9e6681ac0e46bf7d33aeac527e86af8294a35fea4cfd6c750d4f1333287c201837b2b37fce7a440ea58bdb0d907"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ },
+ "scheme": "ed25519"
+ },
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ },
+ "scheme": "ed25519"
+ },
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ },
+ "scheme": "ed25519"
+ },
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f": {
+ "keyid_hash_algorithms": [
+ "sha256"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "4c663b6d0b9f2c74a19b6791c1417c8d1916e2d84461be1a8148e4386c89582d"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/snapshot.json
new file mode 100644
index 0000000..e62aef9
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "6c6b90af042c63f79a7d8079a26bd58fdcb8d84c829fd62aa554b4e9df6d31dfb5a6efb7744b814f7536ace6196c2e0b2768794fa40abf85b49dcc661d0f9a06"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "7a60ff2c4e027e05c318e9a0dc891c46e9899994b50c2ac0dad0bd6f439c69a71486606adf309969f981de6b20630e27f0e529e020c41c638b9f0895ccbd797b"
+ },
+ "length": 2117,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "25ba8ef109680040b2e28dde51389397c51f4ba12acb1ea1b70dfc7e1bc5eceec71b1cbfb4747c47af8d9cdf76baa53ab5b2403eef226be1ae7a8d7794474ebf"
+ },
+ "length": 1392,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets.json
new file mode 100644
index 0000000..163e9e8
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets.json
@@ -0,0 +1,46 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ }
+ },
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..8bca78f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 120000
index 0000000..814339f
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+../../../3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..dc5c46d
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..16b7967
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
@@ -0,0 +1 @@
+4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/timestamp.json
new file mode 100644
index 0000000..028d631
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/4/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "sig": "6341fa975ecf98a7144a458ed4dc6a32b4d3bd0121fc0831679a582a9628cff71f8b13fe5b14f4fc8227f5ae8cfbc40998ed3ccfec84a91ee1e7ce6996678003"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "d1e96adc64da3b4d67016060b8ee9034e486e288595d51d5c24fcd037d4e756a6cff8a9e244da5de9b2bb6ab5bb7f3e7238ffebad0a1a3d50587d01c28f98c15"
+ },
+ "length": 848,
+ "version": 5
+ }
+ },
+ "spec_version": "1.0",
+ "version": 5
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.root.json
new file mode 120000
index 0000000..fb796e3
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.root.json
@@ -0,0 +1 @@
+../../4/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.snapshot.json
new file mode 120000
index 0000000..f7fb098
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.targets.json
new file mode 120000
index 0000000..127f609
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/1.targets.json
@@ -0,0 +1 @@
+../../4/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.root.json
new file mode 120000
index 0000000..5a3e454
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.root.json
@@ -0,0 +1 @@
+../../4/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.snapshot.json
new file mode 120000
index 0000000..5b418d6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.targets.json
new file mode 120000
index 0000000..04b0be4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/2.targets.json
@@ -0,0 +1 @@
+../../4/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.root.json
new file mode 120000
index 0000000..d1cb251
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.root.json
@@ -0,0 +1 @@
+../../4/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.snapshot.json
new file mode 120000
index 0000000..d23c05c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.targets.json
new file mode 120000
index 0000000..2854154
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/3.targets.json
@@ -0,0 +1 @@
+../../4/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.root.json
new file mode 120000
index 0000000..bda6d08
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.root.json
@@ -0,0 +1 @@
+../../4/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.snapshot.json
new file mode 120000
index 0000000..b7aefb2
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.targets.json
new file mode 120000
index 0000000..fef64e4
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/4.targets.json
@@ -0,0 +1 @@
+../../4/repository/4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.root.json
new file mode 120000
index 0000000..f99552a
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.root.json
@@ -0,0 +1 @@
+../../4/repository/5.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.snapshot.json
new file mode 120000
index 0000000..4e98537
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/5.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.targets.json
new file mode 120000
index 0000000..f877144
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/5.targets.json
@@ -0,0 +1 @@
+../../4/repository/5.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/6.snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/6.snapshot.json
new file mode 100644
index 0000000..7c19996
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/6.snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "0e8dc07c124c26108fea54bf637b8133a6b9ba9defee8820ed8ebade9ed1f9b178163e712329179d3fe681e734d7d2078b3714e69e5e9d2a72abad3611c9560e"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "7a60ff2c4e027e05c318e9a0dc891c46e9899994b50c2ac0dad0bd6f439c69a71486606adf309969f981de6b20630e27f0e529e020c41c638b9f0895ccbd797b"
+ },
+ "length": 2117,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "c57a6fd231341bea3adb36d5a9c07167ef26bf9b037470bef1dadfeb2cdafa73b39b30965e39175fa9be5426065073bbf859f149c366ae07ea2a0f6fb6730b00"
+ },
+ "length": 1593,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/6.targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/6.targets.json
new file mode 100644
index 0000000..c528a43
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/6.targets.json
@@ -0,0 +1,52 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ },
+ "5": {
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ },
+ "length": 1
+ }
+ },
+ "version": 6
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/root.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/root.json
new file mode 120000
index 0000000..3d14058
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/root.json
@@ -0,0 +1 @@
+../../4/repository/root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/snapshot.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/snapshot.json
new file mode 100644
index 0000000..7c19996
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/snapshot.json
@@ -0,0 +1,30 @@
+{
+ "signatures": [
+ {
+ "keyid": "bed15bf57ba4deb4e09667099d45d119504420983d16fd0927ebfb0c25184e7f",
+ "sig": "0e8dc07c124c26108fea54bf637b8133a6b9ba9defee8820ed8ebade9ed1f9b178163e712329179d3fe681e734d7d2078b3714e69e5e9d2a72abad3611c9560e"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "root.json": {
+ "hashes": {
+ "sha512": "7a60ff2c4e027e05c318e9a0dc891c46e9899994b50c2ac0dad0bd6f439c69a71486606adf309969f981de6b20630e27f0e529e020c41c638b9f0895ccbd797b"
+ },
+ "length": 2117,
+ "version": 5
+ },
+ "targets.json": {
+ "hashes": {
+ "sha512": "c57a6fd231341bea3adb36d5a9c07167ef26bf9b037470bef1dadfeb2cdafa73b39b30965e39175fa9be5426065073bbf859f149c366ae07ea2a0f6fb6730b00"
+ },
+ "length": 1593,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets.json
new file mode 100644
index 0000000..c528a43
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets.json
@@ -0,0 +1,52 @@
+{
+ "signatures": [
+ {
+ "keyid": "a3eba2113e37b749b908101bdb7b856a44f2853641830c0f939730aca3e30360",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2100-01-01T00:00:00Z",
+ "spec_version": "1.0",
+ "targets": {
+ "0": {
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ },
+ "length": 1
+ },
+ "1": {
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ },
+ "length": 1
+ },
+ "2": {
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ },
+ "length": 1
+ },
+ "3": {
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ },
+ "length": 1
+ },
+ "4": {
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ },
+ "length": 1
+ },
+ "5": {
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ },
+ "length": 1
+ }
+ },
+ "version": 6
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5
new file mode 100644
index 0000000..7813681
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5
@@ -0,0 +1 @@
+5 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..c70b61b
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 120000
index 0000000..1e66cb1
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+../../../4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..a02a231
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..74d1328
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
new file mode 120000
index 0000000..73a65ad
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
@@ -0,0 +1 @@
+../../../4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/timestamp.json b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/timestamp.json
new file mode 100644
index 0000000..85dd0d6
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/consistent-snapshot-true/5/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signatures": [
+ {
+ "keyid": "79b5760e4c96b6becc12bec4260c9c1d533b9b70628ade2b229ef02b772b10b0",
+ "sig": "c928d7bd646cf71f1bc97efef782e5eccb7c88ebe0187708735a39f68dddd4e233702f72ba79a94f27cdb3cab3f568bc3c89598191d2f2dc6cfb968a0906c40d"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha512": "afa8cb95af9715cbf1ef4cf0497daec22e43c96012fd0e34de928c3adef7b4c9e71f10188ffa6d83c06d81c9182421acb03d20bcd7cc32df9b051bf37a728e89"
+ },
+ "length": 848,
+ "version": 6
+ }
+ },
+ "spec_version": "1.0",
+ "version": 6
+ }
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf-transition-M4/generate.go b/client/testdata/go-tuf-transition-M4/generate.go
new file mode 100644
index 0000000..6dc567c
--- /dev/null
+++ b/client/testdata/go-tuf-transition-M4/generate.go
@@ -0,0 +1,148 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "time"
+
+ tuf "github.com/theupdateframework/go-tuf"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+var expirationDate = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
+
+type persistedKeys struct {
+ Encrypted bool `json:"encrypted"`
+ Data []*data.PrivateKey `json:"data"`
+}
+
+func assertNoError(err error) {
+ if err != nil {
+ panic(fmt.Sprintf("assertion failed: %s", err))
+ }
+}
+
+func copyRepo(src string, dst string) {
+ cmd := exec.Command("cp", "-r", src, dst)
+ assertNoError(cmd.Run())
+}
+
+func newRepo(dir string) *tuf.Repo {
+ repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
+ assertNoError(err)
+
+ return repo
+}
+
+func commit(dir string, repo *tuf.Repo) {
+ 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.
+ 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 {
+ assertNoError(repo.AddPrivateKeyWithExpires(role, key, expirationDate))
+ }
+ }
+}
+
+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)
+ assertNoError(os.MkdirAll(filepath.Dir(path), 0755))
+ assertNoError(os.WriteFile(path, data, 0644))
+ paths = append(paths, file)
+ }
+ 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)
+ assertNoError(err)
+ assertNoError(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
+ }
+}
+
+func generateRepos(dir string, consistentSnapshot bool) {
+ f, err := os.Open("../keys.json")
+ assertNoError(err)
+
+ var roleKeys map[string][][]*data.PrivateKey
+ 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.
+ keys := map[string][]*data.PrivateKey{
+ "root": roleKeys["root"][0],
+ "targets": roleKeys["targets"][0],
+ "snapshot": roleKeys["snapshot"][0],
+ "timestamp": roleKeys["timestamp"][0],
+ }
+
+ // Create the initial repo.
+ dir0 := filepath.Join(dir, "0")
+ repo0 := newRepo(dir0)
+ repo0.Init(consistentSnapshot)
+ addKeys(repo0, keys)
+ addTargets(repo0, dir0, map[string][]byte{"0": []byte("0")})
+ commit(dir0, repo0)
+
+ // Rotate all the keys to make sure that works.
+ oldDir := dir0
+ i := 1
+ for _, role := range []string{"root", "targets", "snapshot", "timestamp"} {
+ // Setup the repo.
+ stepName := fmt.Sprintf("%d", i)
+ d := filepath.Join(dir, stepName)
+ copyRepo(oldDir, d)
+ repo := newRepo(d)
+ addKeys(repo, keys)
+
+ // Actually rotate the keys
+ revokeKeys(repo, role, roleKeys[role][0])
+ addKeys(repo, map[string][]*data.PrivateKey{
+ role: roleKeys[role][1],
+ })
+ keys[role] = roleKeys[role][1]
+
+ // Add a target to make sure that works, then commit.
+ addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
+ commit(d, repo)
+
+ i += 1
+ oldDir = d
+ }
+
+ // Add another target file to make sure the workflow worked.
+ stepName := fmt.Sprintf("%d", i)
+ d := filepath.Join(dir, stepName)
+ copyRepo(oldDir, d)
+ repo := newRepo(d)
+ addKeys(repo, keys)
+ addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
+ commit(d, repo)
+}
+
+func main() {
+ cwd, err := os.Getwd()
+ assertNoError(err)
+
+ for _, consistentSnapshot := range []bool{false, true} {
+ name := fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot)
+ log.Printf("generating %s", name)
+ generateRepos(filepath.Join(cwd, name), consistentSnapshot)
+ }
+
+}
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/0/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/1.root.json
new file mode 100644
index 0000000..a9ac50a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add",
+ "sig": "afab8b34f93c99e200505cc6f0d6a9c4e757a7229f96bd31ab618f8bc4c3abe491b372bfec9d231136de46c1e183df194d372360ee9d32652f8c04d7574e2608"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/0/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/root.json
new file mode 100644
index 0000000..a9ac50a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add",
+ "sig": "afab8b34f93c99e200505cc6f0d6a9c4e757a7229f96bd31ab618f8bc4c3abe491b372bfec9d231136de46c1e183df194d372360ee9d32652f8c04d7574e2608"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/0/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/snapshot.json
new file mode 100644
index 0000000..5d2428f
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 588,
+ "hashes": {
+ "sha512": "2532c645262d3a15db2afc561d9ae75db96c3b42abe8bd994c173e45ea70177bc1b080e211d92b6f244d267dcb0a0bc2fbcd1c2b008881c19f950c5e8c0035ec"
+ },
+ "version": 1
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "6efc0efbc7a3b849a58e9cb394af10fe6d199d2ead18e6931932c85f44269d5d73e8f18808d0d9b66fbfd9926b0bda9d2d26a7508a960758c0b84eb8aa31ac0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/0/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/targets.json
new file mode 100644
index 0000000..6a82200
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/targets.json
@@ -0,0 +1,22 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/0/repository/targets/0 b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/targets/0
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/targets/0
@@ -0,0 +1 @@
+0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/0/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/timestamp.json
new file mode 100644
index 0000000..92d160d
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/0/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 617,
+ "hashes": {
+ "sha512": "6fd318e2cc3bca35d2f597e504c095137263ffe3e72f5e5c64e6c7860bea1207448c8994066ffc1cf20898a9d1586cdf09be4e3ea06f69593ed5c7d772f8dfd1"
+ },
+ "version": 1
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8",
+ "sig": "943b219ab6c651793da08d8cf38c7d25fb959c75161b5b86562983841433f9f38c237bb87cf6ecfbb3cb7c78113a3ed2651ee57d3515e7edfc3bebbf349f2809"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/1/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/1.root.json
new file mode 120000
index 0000000..0ffd2b6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/1.root.json
@@ -0,0 +1 @@
+../../0/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/1/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/2.root.json
new file mode 100644
index 0000000..38d2617
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/2.root.json
@@ -0,0 +1,91 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "20e91b55c995989b270091b714347b15169285cb636ef05d68d49ed7b7a96ebfda13898e0d9ef928c382873b9dba90dca492dbf705d56a4b293adaaed574340f"
+ },
+ {
+ "keyid": "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add",
+ "sig": "65f63745aa8bd39132f827c427ea6d87f620ec805d9f376b6a8400dc3db7eb964e5423d31ed08867916d039661a70d6bf255bca552248021a4e78b5d357c2b0f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/1/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/root.json
new file mode 100644
index 0000000..38d2617
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/root.json
@@ -0,0 +1,91 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "20e91b55c995989b270091b714347b15169285cb636ef05d68d49ed7b7a96ebfda13898e0d9ef928c382873b9dba90dca492dbf705d56a4b293adaaed574340f"
+ },
+ {
+ "keyid": "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add",
+ "sig": "65f63745aa8bd39132f827c427ea6d87f620ec805d9f376b6a8400dc3db7eb964e5423d31ed08867916d039661a70d6bf255bca552248021a4e78b5d357c2b0f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/1/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/snapshot.json
new file mode 100644
index 0000000..aed7835
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 789,
+ "hashes": {
+ "sha512": "7a919ed2a5fb7cf80252c9307ac4d27bc2ab54f9bb803389977dcc3d09ae7dff533639c1b19a810d2b212a5c5e904e0f73ab01324ba79dffbb9f7488204caf2a"
+ },
+ "version": 2
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "9a3734441c38daa3275ba60a2178b8dfe5f98296f408bf434144b4bd4f68af73b7f6860e6f2e638b93144ac5bebe361642bbff66a1195596bb8c841429b34508"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets.json
new file mode 100644
index 0000000..0ae4c79
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets.json
@@ -0,0 +1,28 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets/0 b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets/0
new file mode 120000
index 0000000..aced687
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets/0
@@ -0,0 +1 @@
+../../../0/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets/1 b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets/1
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/targets/1
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/1/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/timestamp.json
new file mode 100644
index 0000000..ab60e90
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/1/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 617,
+ "hashes": {
+ "sha512": "d7204efe9ded4f18d679d75054f43b02b20d74189ea81ad90a67399a8884a810e380a4d343e284c4b6fde506e22a464038871d06950166548a84a76344e2bf3a"
+ },
+ "version": 2
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8",
+ "sig": "85c5afdd9dd3777d2cb31721519ab2942edffc9e1282b65b2cede505a469051ac5e86388461db49f948a5998b132dddb5ce0156d21622a6d2201fb93322de20f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/1.root.json
new file mode 120000
index 0000000..cd73051
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/1.root.json
@@ -0,0 +1 @@
+../../1/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/2.root.json
new file mode 120000
index 0000000..bbdcb12
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/2.root.json
@@ -0,0 +1 @@
+../../1/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/3.root.json b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/3.root.json
new file mode 100644
index 0000000..fab2f0a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/3.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "cfe8b0919d1707ca6c23501909b77c8b94d1ede2b85a3081ab7146244cc6fe436b06ad58163919276f2e37ddbcc13a0dde4851301053dcea2a9bcabc5e1cd004"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/root.json
new file mode 100644
index 0000000..fab2f0a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "cfe8b0919d1707ca6c23501909b77c8b94d1ede2b85a3081ab7146244cc6fe436b06ad58163919276f2e37ddbcc13a0dde4851301053dcea2a9bcabc5e1cd004"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/snapshot.json
new file mode 100644
index 0000000..ded8ac9
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 990,
+ "hashes": {
+ "sha512": "987022c23793b3d234d422c3ed4525f8c3771887db610fb2e7d6b455d27ad8b1b4a957a699d519c03754fe4feeb387ec84c95e1edac1d4e9b186bfbdff76ad10"
+ },
+ "version": 3
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "31896c9165a37aa54cf1c4d5f936035e092b1cf4c233ef35cd12c987aaa02de7ec99f26c198984fe0dcc21765d096b25cd67e7ca058613b2b2abef3193dd880d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets.json
new file mode 100644
index 0000000..b22a1d7
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets.json
@@ -0,0 +1,34 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/0 b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/0
new file mode 120000
index 0000000..2ac1d8b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/0
@@ -0,0 +1 @@
+../../../1/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/1 b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/1
new file mode 120000
index 0000000..8e78bc3
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/1
@@ -0,0 +1 @@
+../../../1/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/2 b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/2
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/targets/2
@@ -0,0 +1 @@
+2 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/2/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/timestamp.json
new file mode 100644
index 0000000..4856974
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/2/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 617,
+ "hashes": {
+ "sha512": "70d2f64bc810a0ae1c827c28eed47304c3a9c57402447d129aa3ac851d809a48bd0421aaa99e9e49055984e77f221510415cf27aca8f30f4db1767d6b7bd6024"
+ },
+ "version": 3
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8",
+ "sig": "22367ebcf7c5265260b288e9fec6110257b8c8745af25f2b14c5d347ddab94f46893926bdd37c58d142089419708e446c5e3c1af5c47c51187fe69ca95038605"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/1.root.json
new file mode 120000
index 0000000..fb4fe81
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/1.root.json
@@ -0,0 +1 @@
+../../2/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/2.root.json
new file mode 120000
index 0000000..55f8b9d
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/2.root.json
@@ -0,0 +1 @@
+../../2/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/3.root.json b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/3.root.json
new file mode 120000
index 0000000..b4564b7
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/3.root.json
@@ -0,0 +1 @@
+../../2/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/4.root.json b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/4.root.json
new file mode 100644
index 0000000..8f1b843
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/4.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "3004c4b60645c6cca18e33835837a1639eead05355aa549f1d1304a22304e7058a50fb6188fc3617a47248eb7481de7a625f036cc69b53daf04a103b12768401"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/root.json
new file mode 100644
index 0000000..8f1b843
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "3004c4b60645c6cca18e33835837a1639eead05355aa549f1d1304a22304e7058a50fb6188fc3617a47248eb7481de7a625f036cc69b53daf04a103b12768401"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/snapshot.json
new file mode 100644
index 0000000..41e4ac6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1191,
+ "hashes": {
+ "sha512": "2bf10fb9469cb5f44ccae3c8dac2f70c19e7c58479f74f21aca7a424774ed7ef43295fff3c05f7cfe457b5832366b64e0550874775e277d7233c060e9f94e140"
+ },
+ "version": 4
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "c5efdc1b461ae319c991fd1840771d94dfc8a3e655ac233901a27959e9f2c3e7ab61b83e3d674caf9e2a6043e776d7c32a2c36dbf0922f7f08059d08fc26720a"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets.json
new file mode 100644
index 0000000..8265b26
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets.json
@@ -0,0 +1,40 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/0 b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/0
new file mode 120000
index 0000000..2e6dba0
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/0
@@ -0,0 +1 @@
+../../../2/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/1 b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/1
new file mode 120000
index 0000000..14fee5d
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/1
@@ -0,0 +1 @@
+../../../2/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/2 b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/2
new file mode 120000
index 0000000..dcd19e0
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/2
@@ -0,0 +1 @@
+../../../2/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/3 b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/3
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/targets/3
@@ -0,0 +1 @@
+3 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/3/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/timestamp.json
new file mode 100644
index 0000000..c87022e
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/3/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 618,
+ "hashes": {
+ "sha512": "4ddee1c0c54af1a3dc54897af33c2457525963059c29583e6df65b9b238732070bd8eb470201154f2d2fa5f8bdf37e01a86e6a53c2bcfe0ff302571458c1898d"
+ },
+ "version": 4
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8",
+ "sig": "585cdcc87c23711642449d469df8bedb53a67b65ac75c303d13b7022c8c34c1db70c926e009c2438639af3be1d613f7ce4c7e28020865ad5ad93a5b085a76005"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/1.root.json
new file mode 120000
index 0000000..741d218
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/1.root.json
@@ -0,0 +1 @@
+../../3/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/2.root.json
new file mode 120000
index 0000000..fb9b350
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/2.root.json
@@ -0,0 +1 @@
+../../3/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/3.root.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/3.root.json
new file mode 120000
index 0000000..65e6c5b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/3.root.json
@@ -0,0 +1 @@
+../../3/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/4.root.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/4.root.json
new file mode 120000
index 0000000..c3662ce
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/4.root.json
@@ -0,0 +1 @@
+../../3/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/5.root.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/5.root.json
new file mode 100644
index 0000000..b411336
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/5.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ }
+ },
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "bd2edde6ba4f5cb3a0cc28dca272e2fc0be691575082479f86b82cea4e541a686a0c132c3a7eca231a5782e52e629df92f8f3f89a5b1f57e8b5368986bddfa0e"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/root.json
new file mode 100644
index 0000000..b411336
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ }
+ },
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "bd2edde6ba4f5cb3a0cc28dca272e2fc0be691575082479f86b82cea4e541a686a0c132c3a7eca231a5782e52e629df92f8f3f89a5b1f57e8b5368986bddfa0e"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/snapshot.json
new file mode 100644
index 0000000..489ec31
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1392,
+ "hashes": {
+ "sha512": "93146957ab1f54fe1e8687f408c21a19ad6c033518148bb730b7e635e02f96d9297f1edf12ea5f50354cfcb90385eec9f594d04a08ee876c83759e2314ec8b1a"
+ },
+ "version": 5
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "c9da15fd5b0b359a46503d0a23fa3a4e8e481cb2bdb72d6dbec4852ed6f9d32e186b35071b6ca70ec947e1688458c3476d33991af202b177ffd79ff89955e703"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets.json
new file mode 100644
index 0000000..d81d1a6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets.json
@@ -0,0 +1,46 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ },
+ "4": {
+ "length": 1,
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/0 b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/0
new file mode 120000
index 0000000..efe18e4
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/0
@@ -0,0 +1 @@
+../../../3/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/1 b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/1
new file mode 120000
index 0000000..b4a40c4
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/1
@@ -0,0 +1 @@
+../../../3/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/2 b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/2
new file mode 120000
index 0000000..1c68171
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/2
@@ -0,0 +1 @@
+../../../3/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/3 b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/3
new file mode 120000
index 0000000..1dc4d27
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/3
@@ -0,0 +1 @@
+../../../3/repository/targets/3 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/4 b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/4
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/targets/4
@@ -0,0 +1 @@
+4 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/4/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/timestamp.json
new file mode 100644
index 0000000..1eb6f40
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/4/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 618,
+ "hashes": {
+ "sha512": "066115510f2f7fbb2f5cb0a1c1d00759161d8a92329e9ce54d4b17c06466b5a4423bb8bcc9e47c0a37db5c7d8d3bc01df10c4ea25605778e788a76e3e9ca1e0e"
+ },
+ "version": 5
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315",
+ "sig": "84b9e41c93b32eed4bdb6febe70f9bd4db39bbc7f0c7f820f6a078bbb7146df080a9ceaa237303e3f69e49eb33cc63f8ddff8fba8330904f5fe8d7a66f70400e"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/1.root.json
new file mode 120000
index 0000000..fb796e3
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/1.root.json
@@ -0,0 +1 @@
+../../4/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/2.root.json
new file mode 120000
index 0000000..5a3e454
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/2.root.json
@@ -0,0 +1 @@
+../../4/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/3.root.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/3.root.json
new file mode 120000
index 0000000..d1cb251
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/3.root.json
@@ -0,0 +1 @@
+../../4/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/4.root.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/4.root.json
new file mode 120000
index 0000000..bda6d08
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/4.root.json
@@ -0,0 +1 @@
+../../4/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/5.root.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/5.root.json
new file mode 120000
index 0000000..f99552a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/5.root.json
@@ -0,0 +1 @@
+../../4/repository/5.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/root.json
new file mode 120000
index 0000000..3d14058
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/root.json
@@ -0,0 +1 @@
+../../4/repository/root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/snapshot.json
new file mode 100644
index 0000000..45a607b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 6,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1593,
+ "hashes": {
+ "sha512": "96fa32225e88f9e98db51369fce606039040efc51f098552594132b301228a78c2f2034d059e4d7d6d7bea14ae9862dd8a995056c5f2ed5ff0d6dc09b9da01df"
+ },
+ "version": 6
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "7da0cd1e09be0b8161a1048e08a645e5c8d1558137b2934418bf4e28d4d380b6df2306f5cc89e87e41e5abf079547a709fccd2548bf00fd5acfe5ad2496d670b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets.json
new file mode 100644
index 0000000..241d29b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets.json
@@ -0,0 +1,52 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 6,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ },
+ "4": {
+ "length": 1,
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ }
+ },
+ "5": {
+ "length": 1,
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/0 b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/0
new file mode 120000
index 0000000..94cc287
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/0
@@ -0,0 +1 @@
+../../../4/repository/targets/0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/1 b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/1
new file mode 120000
index 0000000..51c7c9a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/1
@@ -0,0 +1 @@
+../../../4/repository/targets/1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/2 b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/2
new file mode 120000
index 0000000..46e3032
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/2
@@ -0,0 +1 @@
+../../../4/repository/targets/2 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/3 b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/3
new file mode 120000
index 0000000..ca268e9
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/3
@@ -0,0 +1 @@
+../../../4/repository/targets/3 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/4 b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/4
new file mode 120000
index 0000000..19339ad
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/4
@@ -0,0 +1 @@
+../../../4/repository/targets/4 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/5 b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/5
new file mode 100644
index 0000000..7813681
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/targets/5
@@ -0,0 +1 @@
+5 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-false/5/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/timestamp.json
new file mode 100644
index 0000000..1129005
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-false/5/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 6,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 618,
+ "hashes": {
+ "sha512": "7113ee370f6e1b35df7fd8203d27760e8eb8c32195e8a895f140584caff81256ccdf2d03ed43a5efac45ee4f869908cee2275c0f7e6b54969602e0542c9f78f9"
+ },
+ "version": 6
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315",
+ "sig": "85758e7d1cdbedfd3d363136924a6e7878a2b9b68486ecc65658a29f96b55fcfeeda0f94fcae30fa3ceafc3ef5f82df8c68ad2774b6e4f4ea0e6cdbafdce8e0f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.root.json
new file mode 100644
index 0000000..a564c6a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add",
+ "sig": "104eb78cba674adfc7ade1d4396c99466a7d4480e50aab89df53117faffd3d4d4ea87833fe6f83b8dc7f6e1e4a62c0329244c6e5f910627772df3c4d61a8900f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.snapshot.json
new file mode 100644
index 0000000..5d2428f
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 588,
+ "hashes": {
+ "sha512": "2532c645262d3a15db2afc561d9ae75db96c3b42abe8bd994c173e45ea70177bc1b080e211d92b6f244d267dcb0a0bc2fbcd1c2b008881c19f950c5e8c0035ec"
+ },
+ "version": 1
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "6efc0efbc7a3b849a58e9cb394af10fe6d199d2ead18e6931932c85f44269d5d73e8f18808d0d9b66fbfd9926b0bda9d2d26a7508a960758c0b84eb8aa31ac0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.targets.json
new file mode 100644
index 0000000..6a82200
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/1.targets.json
@@ -0,0 +1,22 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/0/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/root.json
new file mode 100644
index 0000000..a564c6a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add",
+ "sig": "104eb78cba674adfc7ade1d4396c99466a7d4480e50aab89df53117faffd3d4d4ea87833fe6f83b8dc7f6e1e4a62c0329244c6e5f910627772df3c4d61a8900f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/0/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/snapshot.json
new file mode 100644
index 0000000..5d2428f
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 588,
+ "hashes": {
+ "sha512": "2532c645262d3a15db2afc561d9ae75db96c3b42abe8bd994c173e45ea70177bc1b080e211d92b6f244d267dcb0a0bc2fbcd1c2b008881c19f950c5e8c0035ec"
+ },
+ "version": 1
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "6efc0efbc7a3b849a58e9cb394af10fe6d199d2ead18e6931932c85f44269d5d73e8f18808d0d9b66fbfd9926b0bda9d2d26a7508a960758c0b84eb8aa31ac0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/0/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/targets.json
new file mode 100644
index 0000000..6a82200
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/targets.json
@@ -0,0 +1,22 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937",
+ "sig": "4d5c3a5da43f3792d077c1128fa28585982ff2957fae59be02a831fc920d0b91cbeaa99fd6c15066ec4da8bf12f993440a90d1624fd7b0a68070e5d60ed2500f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/0/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/timestamp.json
new file mode 100644
index 0000000..92d160d
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/0/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 617,
+ "hashes": {
+ "sha512": "6fd318e2cc3bca35d2f597e504c095137263ffe3e72f5e5c64e6c7860bea1207448c8994066ffc1cf20898a9d1586cdf09be4e3ea06f69593ed5c7d772f8dfd1"
+ },
+ "version": 1
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8",
+ "sig": "943b219ab6c651793da08d8cf38c7d25fb959c75161b5b86562983841433f9f38c237bb87cf6ecfbb3cb7c78113a3ed2651ee57d3515e7edfc3bebbf349f2809"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.root.json
new file mode 120000
index 0000000..0ffd2b6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.root.json
@@ -0,0 +1 @@
+../../0/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.snapshot.json
new file mode 120000
index 0000000..840f843
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../0/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.targets.json
new file mode 120000
index 0000000..4d11148
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/1.targets.json
@@ -0,0 +1 @@
+../../0/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.root.json
new file mode 100644
index 0000000..92ff6b2
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.root.json
@@ -0,0 +1,91 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "7ac71619b21fe3a076fbbf3e17f92177c5374f005c32f1818e7c92eee107e3c726ccf906e800878035a9caf7679610147d72cb515cd76164b08b5c8bf93c0f0e"
+ },
+ {
+ "keyid": "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add",
+ "sig": "e560f386c0c270bb44cedda965ad21526e65162b41a2c30d84fb3a80f58913432e8dc74e5e7b0635ccdf51a2b951dae3b0ba28b4aac4088641a2b2cd2933dd04"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.snapshot.json
new file mode 100644
index 0000000..aed7835
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 789,
+ "hashes": {
+ "sha512": "7a919ed2a5fb7cf80252c9307ac4d27bc2ab54f9bb803389977dcc3d09ae7dff533639c1b19a810d2b212a5c5e904e0f73ab01324ba79dffbb9f7488204caf2a"
+ },
+ "version": 2
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "9a3734441c38daa3275ba60a2178b8dfe5f98296f408bf434144b4bd4f68af73b7f6860e6f2e638b93144ac5bebe361642bbff66a1195596bb8c841429b34508"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.targets.json
new file mode 100644
index 0000000..0ae4c79
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/2.targets.json
@@ -0,0 +1,28 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/root.json
new file mode 100644
index 0000000..92ff6b2
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/root.json
@@ -0,0 +1,91 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "7ac71619b21fe3a076fbbf3e17f92177c5374f005c32f1818e7c92eee107e3c726ccf906e800878035a9caf7679610147d72cb515cd76164b08b5c8bf93c0f0e"
+ },
+ {
+ "keyid": "ce72db3f938914205461a415c9b7b91267a2079df991fd6283aa8461988c1add",
+ "sig": "e560f386c0c270bb44cedda965ad21526e65162b41a2c30d84fb3a80f58913432e8dc74e5e7b0635ccdf51a2b951dae3b0ba28b4aac4088641a2b2cd2933dd04"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/snapshot.json
new file mode 100644
index 0000000..aed7835
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 789,
+ "hashes": {
+ "sha512": "7a919ed2a5fb7cf80252c9307ac4d27bc2ab54f9bb803389977dcc3d09ae7dff533639c1b19a810d2b212a5c5e904e0f73ab01324ba79dffbb9f7488204caf2a"
+ },
+ "version": 2
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "9a3734441c38daa3275ba60a2178b8dfe5f98296f408bf434144b4bd4f68af73b7f6860e6f2e638b93144ac5bebe361642bbff66a1195596bb8c841429b34508"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets.json
new file mode 100644
index 0000000..0ae4c79
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets.json
@@ -0,0 +1,28 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "808aa256a8172bb0cb961767c6768e55ccf732c99afccc6145752d7a328b7937",
+ "sig": "81bcf5921834a20b8b0946815ad4316f43ffce20b3793e6653dc5fd4b98f1c6318ce2243948574e82414a102717a6f2d731250b3191c54abc8391fd867503e0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..b0c459f
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../0/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/1/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/timestamp.json
new file mode 100644
index 0000000..ab60e90
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/1/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 617,
+ "hashes": {
+ "sha512": "d7204efe9ded4f18d679d75054f43b02b20d74189ea81ad90a67399a8884a810e380a4d343e284c4b6fde506e22a464038871d06950166548a84a76344e2bf3a"
+ },
+ "version": 2
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8",
+ "sig": "85c5afdd9dd3777d2cb31721519ab2942edffc9e1282b65b2cede505a469051ac5e86388461db49f948a5998b132dddb5ce0156d21622a6d2201fb93322de20f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.root.json
new file mode 120000
index 0000000..cd73051
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.root.json
@@ -0,0 +1 @@
+../../1/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.snapshot.json
new file mode 120000
index 0000000..1307f57
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../1/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.targets.json
new file mode 120000
index 0000000..85e1ecc
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/1.targets.json
@@ -0,0 +1 @@
+../../1/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.root.json
new file mode 120000
index 0000000..bbdcb12
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.root.json
@@ -0,0 +1 @@
+../../1/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.snapshot.json
new file mode 120000
index 0000000..24d1075
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../1/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.targets.json
new file mode 120000
index 0000000..f6a1749
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/2.targets.json
@@ -0,0 +1 @@
+../../1/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.root.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.root.json
new file mode 100644
index 0000000..3b1c2e2
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "475436056dca79fd151ae830ad2f4dbc7ecc5b146a79319c20a1167d4f214047d8f3847d1cda4ccd32a48acb13acbea55ba7e8254cffaa07e3de9ecdd0c13d03"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.snapshot.json
new file mode 100644
index 0000000..ded8ac9
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 990,
+ "hashes": {
+ "sha512": "987022c23793b3d234d422c3ed4525f8c3771887db610fb2e7d6b455d27ad8b1b4a957a699d519c03754fe4feeb387ec84c95e1edac1d4e9b186bfbdff76ad10"
+ },
+ "version": 3
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "31896c9165a37aa54cf1c4d5f936035e092b1cf4c233ef35cd12c987aaa02de7ec99f26c198984fe0dcc21765d096b25cd67e7ca058613b2b2abef3193dd880d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.targets.json
new file mode 100644
index 0000000..b22a1d7
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/3.targets.json
@@ -0,0 +1,34 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/root.json
new file mode 100644
index 0000000..3b1c2e2
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ },
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "475436056dca79fd151ae830ad2f4dbc7ecc5b146a79319c20a1167d4f214047d8f3847d1cda4ccd32a48acb13acbea55ba7e8254cffaa07e3de9ecdd0c13d03"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/snapshot.json
new file mode 100644
index 0000000..ded8ac9
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 990,
+ "hashes": {
+ "sha512": "987022c23793b3d234d422c3ed4525f8c3771887db610fb2e7d6b455d27ad8b1b4a957a699d519c03754fe4feeb387ec84c95e1edac1d4e9b186bfbdff76ad10"
+ },
+ "version": 3
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "289e5a9e71afd7909326aa4caea92f7557ee0e2283d8c31f0c3401ce67248a45",
+ "sig": "31896c9165a37aa54cf1c4d5f936035e092b1cf4c233ef35cd12c987aaa02de7ec99f26c198984fe0dcc21765d096b25cd67e7ca058613b2b2abef3193dd880d"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets.json
new file mode 100644
index 0000000..b22a1d7
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets.json
@@ -0,0 +1,34 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "09593c7b47f4dbf1e8ac949fa010279ffbbd36070a54a89b689378441e8111602d4236fa2962063778c82027d4746a8973ba8fea86e9c105190da9fa362d8b0c"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..de9aac3
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../1/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+2 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..2ebb2d3
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../1/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/2/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/timestamp.json
new file mode 100644
index 0000000..4856974
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/2/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 3,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 617,
+ "hashes": {
+ "sha512": "70d2f64bc810a0ae1c827c28eed47304c3a9c57402447d129aa3ac851d809a48bd0421aaa99e9e49055984e77f221510415cf27aca8f30f4db1767d6b7bd6024"
+ },
+ "version": 3
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8",
+ "sig": "22367ebcf7c5265260b288e9fec6110257b8c8745af25f2b14c5d347ddab94f46893926bdd37c58d142089419708e446c5e3c1af5c47c51187fe69ca95038605"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.root.json
new file mode 120000
index 0000000..fb4fe81
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.root.json
@@ -0,0 +1 @@
+../../2/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.snapshot.json
new file mode 120000
index 0000000..4dfb0d0
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.targets.json
new file mode 120000
index 0000000..1eeb564
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/1.targets.json
@@ -0,0 +1 @@
+../../2/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.root.json
new file mode 120000
index 0000000..55f8b9d
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.root.json
@@ -0,0 +1 @@
+../../2/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.snapshot.json
new file mode 120000
index 0000000..65445cb
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.targets.json
new file mode 120000
index 0000000..3600a0f
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/2.targets.json
@@ -0,0 +1 @@
+../../2/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.root.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.root.json
new file mode 120000
index 0000000..b4564b7
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.root.json
@@ -0,0 +1 @@
+../../2/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.snapshot.json
new file mode 120000
index 0000000..7d86b95
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../2/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.targets.json
new file mode 120000
index 0000000..d1df10e
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/3.targets.json
@@ -0,0 +1 @@
+../../2/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.root.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.root.json
new file mode 100644
index 0000000..a389f49
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "15431a0217deb636e2e79876cfdfc8b78fdda1119e6c8512c7dc61af240a7d2303f172e7fc28de7dfe58d92c84f5118ccd09d0f29f49aaa3f7ef95edf21e2d0a"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.snapshot.json
new file mode 100644
index 0000000..41e4ac6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1191,
+ "hashes": {
+ "sha512": "2bf10fb9469cb5f44ccae3c8dac2f70c19e7c58479f74f21aca7a424774ed7ef43295fff3c05f7cfe457b5832366b64e0550874775e277d7233c060e9f94e140"
+ },
+ "version": 4
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "c5efdc1b461ae319c991fd1840771d94dfc8a3e655ac233901a27959e9f2c3e7ab61b83e3d674caf9e2a6043e776d7c32a2c36dbf0922f7f08059d08fc26720a"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.targets.json
new file mode 100644
index 0000000..8265b26
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/4.targets.json
@@ -0,0 +1,40 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/root.json
new file mode 100644
index 0000000..a389f49
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ },
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "15431a0217deb636e2e79876cfdfc8b78fdda1119e6c8512c7dc61af240a7d2303f172e7fc28de7dfe58d92c84f5118ccd09d0f29f49aaa3f7ef95edf21e2d0a"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/snapshot.json
new file mode 100644
index 0000000..41e4ac6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1191,
+ "hashes": {
+ "sha512": "2bf10fb9469cb5f44ccae3c8dac2f70c19e7c58479f74f21aca7a424774ed7ef43295fff3c05f7cfe457b5832366b64e0550874775e277d7233c060e9f94e140"
+ },
+ "version": 4
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "c5efdc1b461ae319c991fd1840771d94dfc8a3e655ac233901a27959e9f2c3e7ab61b83e3d674caf9e2a6043e776d7c32a2c36dbf0922f7f08059d08fc26720a"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets.json
new file mode 100644
index 0000000..8265b26
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets.json
@@ -0,0 +1,40 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "7bfe0a63ee324034807b5341336d9d2d64e9ef3936086577b5bbcc6d021b4656bd6bf14d817bbb3908e4dcb05391d1b4031b527c14d942c2d1e38275d5ff1308"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..f820397
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../2/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+3 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..7cbcc2a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../2/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..180cdb4
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../2/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/3/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/timestamp.json
new file mode 100644
index 0000000..c87022e
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/3/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 4,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 618,
+ "hashes": {
+ "sha512": "4ddee1c0c54af1a3dc54897af33c2457525963059c29583e6df65b9b238732070bd8eb470201154f2d2fa5f8bdf37e01a86e6a53c2bcfe0ff302571458c1898d"
+ },
+ "version": 4
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "aa3255b4e8e17e566d2bdbea0e5842978f9fa1d2fa9ec76ae76b146164acbfc8",
+ "sig": "585cdcc87c23711642449d469df8bedb53a67b65ac75c303d13b7022c8c34c1db70c926e009c2438639af3be1d613f7ce4c7e28020865ad5ad93a5b085a76005"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.root.json
new file mode 120000
index 0000000..741d218
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.root.json
@@ -0,0 +1 @@
+../../3/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.snapshot.json
new file mode 120000
index 0000000..f87f7ed
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.targets.json
new file mode 120000
index 0000000..b0002ec
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/1.targets.json
@@ -0,0 +1 @@
+../../3/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.root.json
new file mode 120000
index 0000000..fb9b350
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.root.json
@@ -0,0 +1 @@
+../../3/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.snapshot.json
new file mode 120000
index 0000000..e96983b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.targets.json
new file mode 120000
index 0000000..dbb2a75
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/2.targets.json
@@ -0,0 +1 @@
+../../3/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.root.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.root.json
new file mode 120000
index 0000000..65e6c5b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.root.json
@@ -0,0 +1 @@
+../../3/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.snapshot.json
new file mode 120000
index 0000000..f2fd560
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.targets.json
new file mode 120000
index 0000000..ca61e77
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/3.targets.json
@@ -0,0 +1 @@
+../../3/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.root.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.root.json
new file mode 120000
index 0000000..c3662ce
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.root.json
@@ -0,0 +1 @@
+../../3/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.snapshot.json
new file mode 120000
index 0000000..a2f839a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.snapshot.json
@@ -0,0 +1 @@
+../../3/repository/4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.targets.json
new file mode 120000
index 0000000..9f7a784
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/4.targets.json
@@ -0,0 +1 @@
+../../3/repository/4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.root.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.root.json
new file mode 100644
index 0000000..dcb0e92
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ }
+ },
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "2b6f30258eaf8b4b2d900a2efb4a597b45a9c0c5a0720d5521dd37d63c34d1db62aafaa1d95b6d881f9c991615ab68930c24ef247d2f45836bca3659e36d9c0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.snapshot.json
new file mode 100644
index 0000000..489ec31
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1392,
+ "hashes": {
+ "sha512": "93146957ab1f54fe1e8687f408c21a19ad6c033518148bb730b7e635e02f96d9297f1edf12ea5f50354cfcb90385eec9f594d04a08ee876c83759e2314ec8b1a"
+ },
+ "version": 5
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "c9da15fd5b0b359a46503d0a23fa3a4e8e481cb2bdb72d6dbec4852ed6f9d32e186b35071b6ca70ec947e1688458c3476d33991af202b177ffd79ff89955e703"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.targets.json
new file mode 100644
index 0000000..d81d1a6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/5.targets.json
@@ -0,0 +1,46 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ },
+ "4": {
+ "length": 1,
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/root.json
new file mode 100644
index 0000000..dcb0e92
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "keys": {
+ "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ }
+ },
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ },
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ },
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": true
+ },
+ "signatures": [
+ {
+ "keyid": "b2403f96ae9b1089d8cbc15bbc35e9acbacd7984571f951b43aab56aedcfa84f",
+ "sig": "2b6f30258eaf8b4b2d900a2efb4a597b45a9c0c5a0720d5521dd37d63c34d1db62aafaa1d95b6d881f9c991615ab68930c24ef247d2f45836bca3659e36d9c0b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/snapshot.json
new file mode 100644
index 0000000..489ec31
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1392,
+ "hashes": {
+ "sha512": "93146957ab1f54fe1e8687f408c21a19ad6c033518148bb730b7e635e02f96d9297f1edf12ea5f50354cfcb90385eec9f594d04a08ee876c83759e2314ec8b1a"
+ },
+ "version": 5
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "c9da15fd5b0b359a46503d0a23fa3a4e8e481cb2bdb72d6dbec4852ed6f9d32e186b35071b6ca70ec947e1688458c3476d33991af202b177ffd79ff89955e703"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets.json
new file mode 100644
index 0000000..d81d1a6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets.json
@@ -0,0 +1,46 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ },
+ "4": {
+ "length": 1,
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "68f2346cdbe045e8558b2ccd86be66e15466955167557c704b51d7163838f670c53ab9247c16a4ed0cd4ecc981a7e2a04a350b01548f97654499d6f9c17c4202"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..8bca78f
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../3/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 120000
index 0000000..814339f
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+../../../3/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..dc5c46d
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../3/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..16b7967
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../3/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
@@ -0,0 +1 @@
+4 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/4/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/timestamp.json
new file mode 100644
index 0000000..1eb6f40
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/4/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 5,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 618,
+ "hashes": {
+ "sha512": "066115510f2f7fbb2f5cb0a1c1d00759161d8a92329e9ce54d4b17c06466b5a4423bb8bcc9e47c0a37db5c7d8d3bc01df10c4ea25605778e788a76e3e9ca1e0e"
+ },
+ "version": 5
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315",
+ "sig": "84b9e41c93b32eed4bdb6febe70f9bd4db39bbc7f0c7f820f6a078bbb7146df080a9ceaa237303e3f69e49eb33cc63f8ddff8fba8330904f5fe8d7a66f70400e"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.root.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.root.json
new file mode 120000
index 0000000..fb796e3
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.root.json
@@ -0,0 +1 @@
+../../4/repository/1.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.snapshot.json
new file mode 120000
index 0000000..f7fb098
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/1.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.targets.json
new file mode 120000
index 0000000..127f609
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/1.targets.json
@@ -0,0 +1 @@
+../../4/repository/1.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.root.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.root.json
new file mode 120000
index 0000000..5a3e454
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.root.json
@@ -0,0 +1 @@
+../../4/repository/2.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.snapshot.json
new file mode 120000
index 0000000..5b418d6
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/2.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.targets.json
new file mode 120000
index 0000000..04b0be4
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/2.targets.json
@@ -0,0 +1 @@
+../../4/repository/2.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.root.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.root.json
new file mode 120000
index 0000000..d1cb251
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.root.json
@@ -0,0 +1 @@
+../../4/repository/3.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.snapshot.json
new file mode 120000
index 0000000..d23c05c
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/3.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.targets.json
new file mode 120000
index 0000000..2854154
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/3.targets.json
@@ -0,0 +1 @@
+../../4/repository/3.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.root.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.root.json
new file mode 120000
index 0000000..bda6d08
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.root.json
@@ -0,0 +1 @@
+../../4/repository/4.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.snapshot.json
new file mode 120000
index 0000000..b7aefb2
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/4.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.targets.json
new file mode 120000
index 0000000..fef64e4
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/4.targets.json
@@ -0,0 +1 @@
+../../4/repository/4.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.root.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.root.json
new file mode 120000
index 0000000..f99552a
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.root.json
@@ -0,0 +1 @@
+../../4/repository/5.root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.snapshot.json
new file mode 120000
index 0000000..4e98537
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.snapshot.json
@@ -0,0 +1 @@
+../../4/repository/5.snapshot.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.targets.json
new file mode 120000
index 0000000..f877144
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/5.targets.json
@@ -0,0 +1 @@
+../../4/repository/5.targets.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/6.snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/6.snapshot.json
new file mode 100644
index 0000000..45a607b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/6.snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 6,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1593,
+ "hashes": {
+ "sha512": "96fa32225e88f9e98db51369fce606039040efc51f098552594132b301228a78c2f2034d059e4d7d6d7bea14ae9862dd8a995056c5f2ed5ff0d6dc09b9da01df"
+ },
+ "version": 6
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "7da0cd1e09be0b8161a1048e08a645e5c8d1558137b2934418bf4e28d4d380b6df2306f5cc89e87e41e5abf079547a709fccd2548bf00fd5acfe5ad2496d670b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/6.targets.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/6.targets.json
new file mode 100644
index 0000000..241d29b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/6.targets.json
@@ -0,0 +1,52 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 6,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ },
+ "4": {
+ "length": 1,
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ }
+ },
+ "5": {
+ "length": 1,
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/root.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/root.json
new file mode 120000
index 0000000..3d14058
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/root.json
@@ -0,0 +1 @@
+../../4/repository/root.json \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/snapshot.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/snapshot.json
new file mode 100644
index 0000000..45a607b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/snapshot.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "snapshot",
+ "spec_version": "1.0",
+ "version": 6,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "length": 1593,
+ "hashes": {
+ "sha512": "96fa32225e88f9e98db51369fce606039040efc51f098552594132b301228a78c2f2034d059e4d7d6d7bea14ae9862dd8a995056c5f2ed5ff0d6dc09b9da01df"
+ },
+ "version": 6
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "b096bc2d67080cec22e6c0bbaa69cfd9d714b9e9ad847d255f950b2728855fef",
+ "sig": "7da0cd1e09be0b8161a1048e08a645e5c8d1558137b2934418bf4e28d4d380b6df2306f5cc89e87e41e5abf079547a709fccd2548bf00fd5acfe5ad2496d670b"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets.json
new file mode 100644
index 0000000..241d29b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets.json
@@ -0,0 +1,52 @@
+{
+ "signed": {
+ "_type": "targets",
+ "spec_version": "1.0",
+ "version": 6,
+ "expires": "2100-01-01T00:00:00Z",
+ "targets": {
+ "0": {
+ "length": 1,
+ "hashes": {
+ "sha512": "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99"
+ }
+ },
+ "1": {
+ "length": 1,
+ "hashes": {
+ "sha512": "4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a"
+ }
+ },
+ "2": {
+ "length": 1,
+ "hashes": {
+ "sha512": "40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114"
+ }
+ },
+ "3": {
+ "length": 1,
+ "hashes": {
+ "sha512": "3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb"
+ }
+ },
+ "4": {
+ "length": 1,
+ "hashes": {
+ "sha512": "a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568"
+ }
+ },
+ "5": {
+ "length": 1,
+ "hashes": {
+ "sha512": "06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25"
+ }
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "bc8f087663855564b358ddf314d5932ab920af17bd1df2f58b397afd82d76dc1",
+ "sig": "1e80ae84c4badf194e2fd7225c120999d8f628598fa0e994a2ff7cac705ec2f14601a64ba5370fc668f3fb114975dd81c554400d757f41762c4e12eb4db35d02"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5 b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5
new file mode 100644
index 0000000..7813681
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/06df05371981a237d0ed11472fae7c94c9ac0eff1d05413516710d17b10a4fb6f4517bda4a695f02d0a73dd4db543b4653df28f5d09dab86f92ffb9b86d01e25.5
@@ -0,0 +1 @@
+5 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
new file mode 120000
index 0000000..c70b61b
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0
@@ -0,0 +1 @@
+../../../4/repository/targets/31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99.0 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
new file mode 120000
index 0000000..1e66cb1
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3
@@ -0,0 +1 @@
+../../../4/repository/targets/3bafbf08882a2d10133093a1b8433f50563b93c14acd05b79028eb1d12799027241450980651994501423a66c276ae26c43b739bc65c4e16b10c3af6c202aebb.3 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
new file mode 120000
index 0000000..a02a231
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2
@@ -0,0 +1 @@
+../../../4/repository/targets/40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b7efad6ef9ff4683da1eda0244448cb343aa688f5d3efd7314dafe580ac0bcbf115aeca9e8dc114.2 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
new file mode 120000
index 0000000..74d1328
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1
@@ -0,0 +1 @@
+../../../4/repository/targets/4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a.1 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
new file mode 120000
index 0000000..73a65ad
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4
@@ -0,0 +1 @@
+../../../4/repository/targets/a321d8b405e3ef2604959847b36d171eebebc4a8941dc70a4784935a4fca5d5813de84dfa049f06549aa61b20848c1633ce81b675286ea8fb53db240d831c568.4 \ No newline at end of file
diff --git a/client/testdata/go-tuf/consistent-snapshot-true/5/repository/timestamp.json b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/timestamp.json
new file mode 100644
index 0000000..1129005
--- /dev/null
+++ b/client/testdata/go-tuf/consistent-snapshot-true/5/repository/timestamp.json
@@ -0,0 +1,23 @@
+{
+ "signed": {
+ "_type": "timestamp",
+ "spec_version": "1.0",
+ "version": 6,
+ "expires": "2100-01-01T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "length": 618,
+ "hashes": {
+ "sha512": "7113ee370f6e1b35df7fd8203d27760e8eb8c32195e8a895f140584caff81256ccdf2d03ed43a5efac45ee4f869908cee2275c0f7e6b54969602e0542c9f78f9"
+ },
+ "version": 6
+ }
+ }
+ },
+ "signatures": [
+ {
+ "keyid": "a8eaf6de5aecfd0a72b60295b1e1cd12f349079ebcbbb63dbe7072f162e85315",
+ "sig": "85758e7d1cdbedfd3d363136924a6e7878a2b9b68486ecc65658a29f96b55fcfeeda0f94fcae30fa3ceafc3ef5f82df8c68ad2774b6e4f4ea0e6cdbafdce8e0f"
+ }
+ ]
+} \ No newline at end of file
diff --git a/client/testdata/go-tuf/generate.go b/client/testdata/go-tuf/generate.go
new file mode 100644
index 0000000..51629bd
--- /dev/null
+++ b/client/testdata/go-tuf/generate.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "log"
+ "os"
+ "path/filepath"
+
+ "github.com/theupdateframework/go-tuf/client/testdata/go-tuf/generator"
+)
+
+func main() {
+ cwd, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ generator.Generate(filepath.Join(cwd, "consistent-snapshot-false"), "../keys.json", false)
+ generator.Generate(filepath.Join(cwd, "consistent-snapshot-true"), "../keys.json", true)
+}
diff --git a/client/testdata/go-tuf/generator/generator.go b/client/testdata/go-tuf/generator/generator.go
new file mode 100644
index 0000000..43903ac
--- /dev/null
+++ b/client/testdata/go-tuf/generator/generator.go
@@ -0,0 +1,177 @@
+package generator
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+ "time"
+
+ tuf "github.com/theupdateframework/go-tuf"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+var expirationDate = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
+
+type persistedKeys struct {
+ Encrypted bool `json:"encrypted"`
+ Data []*data.PrivateKey `json:"data"`
+}
+
+func assertNoError(err error) {
+ if err != nil {
+ panic(fmt.Sprintf("assertion failed: %s", err))
+ }
+}
+
+// copyRepo recursively copies all regular files and directories under src
+// to dst. In the case where any destination file/directory exists
+// (including dst itself), an error is returned.
+func copyRepo(src string, dst string) error {
+ copyToDest := func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ rel, err := filepath.Rel(src, path)
+ if err != nil {
+ return err
+ }
+ target := filepath.Join(dst, rel)
+ mode := info.Mode()
+ if mode.IsDir() {
+ return os.Mkdir(target, mode.Perm())
+ } else if mode.IsRegular() {
+ sfile, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer sfile.Close()
+ dfile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode.Perm())
+ if err != nil {
+ return err
+ }
+ defer dfile.Close()
+ if _, err := io.Copy(dfile, sfile); err != nil {
+ return err
+ }
+ return nil
+ }
+ return fmt.Errorf("unknown mode %v", mode)
+ }
+ return filepath.Walk(src, copyToDest)
+}
+
+func newRepo(dir string) *tuf.Repo {
+ repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
+ assertNoError(err)
+
+ return repo
+}
+
+func commit(dir string, repo *tuf.Repo) {
+ 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.
+ 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)
+ assertNoError(err)
+ assertNoError(repo.AddPrivateKeyWithExpires(role, signer, expirationDate))
+ }
+ }
+}
+
+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)
+ assertNoError(os.MkdirAll(filepath.Dir(path), 0755))
+ assertNoError(os.WriteFile(path, data, 0644))
+ paths = append(paths, file)
+ }
+ 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)
+ assertNoError(err)
+ assertNoError(repo.RevokeKeyWithExpires(role, signer.PublicData().IDs()[0], expirationDate))
+ }
+}
+
+func generateRepos(dir string, roleKeys map[string][][]*data.PrivateKey, consistentSnapshot bool) {
+ // Collect all the initial keys we'll use when creating repositories.
+ // We'll modify this to reflect rotated keys.
+ keys := map[string][]*data.PrivateKey{
+ "root": roleKeys["root"][0],
+ "targets": roleKeys["targets"][0],
+ "snapshot": roleKeys["snapshot"][0],
+ "timestamp": roleKeys["timestamp"][0],
+ }
+
+ // Create the initial repo.
+ dir0 := filepath.Join(dir, "0")
+ repo0 := newRepo(dir0)
+ repo0.Init(consistentSnapshot)
+ addKeys(repo0, keys)
+ addTargets(repo0, dir0, map[string][]byte{"0": []byte("0")})
+ commit(dir0, repo0)
+
+ // Rotate all the keys to make sure that works.
+ oldDir := dir0
+ i := 1
+ for _, role := range []string{"root", "targets", "snapshot", "timestamp"} {
+ // Setup the repo.
+ stepName := fmt.Sprintf("%d", i)
+ d := filepath.Join(dir, stepName)
+ assertNoError(copyRepo(oldDir, d))
+ repo := newRepo(d)
+ addKeys(repo, keys)
+
+ // Actually rotate the keys
+ revokeKeys(repo, role, roleKeys[role][0])
+ addKeys(repo, map[string][]*data.PrivateKey{
+ role: roleKeys[role][1],
+ })
+ keys[role] = roleKeys[role][1]
+
+ // Add a target to make sure that works, then commit.
+ addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
+ commit(d, repo)
+
+ i += 1
+ oldDir = d
+ }
+
+ // Add another target file to make sure the workflow worked.
+ stepName := fmt.Sprintf("%d", i)
+ d := filepath.Join(dir, stepName)
+ assertNoError(copyRepo(oldDir, d))
+ repo := newRepo(d)
+ addKeys(repo, keys)
+ addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
+ commit(d, repo)
+}
+
+func Generate(dir string, keysPath string, consistentSnapshot bool) {
+ f, err := os.Open(keysPath)
+ assertNoError(err)
+
+ var roleKeys map[string][][]*data.PrivateKey
+ assertNoError(json.NewDecoder(f).Decode(&roleKeys))
+
+ log.Printf("generating %s", dir)
+
+ generateRepos(dir, roleKeys, consistentSnapshot)
+}
diff --git a/client/testdata/go-tuf/regenerate-metadata.sh b/client/testdata/go-tuf/regenerate-metadata.sh
new file mode 100755
index 0000000..ea286a0
--- /dev/null
+++ b/client/testdata/go-tuf/regenerate-metadata.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -eu
+
+cd `dirname $0`
+
+for d in consistent-snapshot-false consistent-snapshot-true; do
+ if [[ -e $d ]]; then
+ rm -r $d
+ fi
+done
+
+go run generate.go
+go run ../tools/linkify-metadata.go
diff --git a/client/testdata/keys.json b/client/testdata/keys.json
new file mode 100644
index 0000000..0edf9e3
--- /dev/null
+++ b/client/testdata/keys.json
@@ -0,0 +1,122 @@
+{
+ "root": [
+ [
+ {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca",
+ "private": "68c2a06583ce83dabb1fc5d2b3697015b936f341f21be1be1397670a650ebd53bca152214dbfd3e7a1b7a7b3e2cc179fb00520fd7212c8a60ba99f14dfc0e1ca"
+ }
+ }
+ ],
+ [
+ {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54",
+ "private": "e53595d99a85a33d7c228d28b25c856148c65be36558d0d61faa7e018a51db5c153986a7370e002f377dd68797e7466c421180c1571e233de2f8ab41c8af4f54"
+ }
+ }
+ ]
+ ],
+ "snapshot": [
+ [
+ {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94",
+ "private": "28de1763cc4cc78702167bd6cf862fc3977afc2404c5eebbdfb948bab55f14f0d482fa4805a50870aa1356ace6b764f7ab47ed4dc38f49b1a189afa25f179e94"
+ }
+ }
+ ],
+ [
+ {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f",
+ "private": "fb85703e9c1c77cfe87c18eda24d5637d997503fef4107f6c04777b0fbec2b8c722dbc861813bb5b568524d62083e875c08e66fed1694d9161d253fa163dd86f"
+ }
+ }
+ ]
+ ],
+ "targets": [
+ [
+ {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a",
+ "private": "dee6d1fa7f041bee48a4e783d416e12c72ecdbd164202a71b23149adbb1c3d1f62f71f99c788f16bcdc8bb252455e3a690350e4ddea5a6aab1f9a3aaabcf369a"
+ }
+ }
+ ],
+ [
+ {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "aee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42",
+ "private": "9f51d6d05cda79b80dad73d7e1611df50555d5090bf9fe6bfac5ad3690b57e2aaee574e56780ea1709a8b56e02d4ffdd9a3a1deaed61e2eb0701f376a4422e42"
+ }
+ }
+ ]
+ ],
+ "timestamp": [
+ [
+ {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "8656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160",
+ "private": "2ef838a6f0aa4bc0a746b8086098683f96bb5118c4dca7d3caa3dee5c86ea8398656ad20568cd3534c405e4d9a84b0c6e6163f7f66434df77416502835b9b160"
+ }
+ }
+ ],
+ [
+ {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "3ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76",
+ "private": "1d9a579675ee85074a7c5d8a3e4a58a21e82bddc53bbfe1bef547d681d4049783ab34b0c2d4eadccaa0f0cf22ced07b552394063a9de2806993d022360dffc76"
+ }
+ }
+ ]
+ ]
+}
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/__init__.py b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/__init__.py
new file mode 100644
index 0000000..36285a6
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/__init__.py
@@ -0,0 +1,35 @@
+# Delegation tree
+#
+# Targets
+# / \
+# a f
+# / \
+# b e
+# / \
+# c d
+#
+# No terminating delegations.
+#
+# Roles should be evaluated in the order:
+# Targets > a > b > c > d > e > f
+
+from fixtures.builder import FixtureBuilder
+
+
+def build():
+ FixtureBuilder('TUFTestFixture3LevelDelegation')\
+ .publish(with_client=True)\
+ .create_target('targets.txt')\
+ .delegate('a', ['*.txt'])\
+ .create_target('a.txt', signing_role='a')\
+ .delegate('b', ['*.txt'], parent='a') \
+ .create_target('b.txt', signing_role='b') \
+ .delegate('c', ['*.txt'], parent='b') \
+ .create_target('c.txt', signing_role='c') \
+ .delegate('d', ['*.txt'], parent='b') \
+ .create_target('d.txt', signing_role='d') \
+ .delegate('e', ['*.txt'], parent='a') \
+ .create_target('e.txt', signing_role='e') \
+ .delegate('f', ['*.txt']) \
+ .create_target('f.txt', signing_role='f') \
+ .publish()
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.root.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.root.json
new file mode 100644
index 0000000..0898610
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "d0bf76a5cfc0aee1b8a1b1bf0ed8ca646a1a6d5f205945c515e8546bfd3c1e6b5e07cc0b93836bd030dd05ba68f177aecb05f6bf90c6702fd178e53310022506"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2020-12-31T05:48:20Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.snapshot.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.snapshot.json
new file mode 100644
index 0000000..dcbd2f4
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "61db8765350398f7f750853337d9a55c5d6e790812d29146b5b45d5fd43d2a42c474a7a9fab263c3a50a28114a82f79dbf24ff1f99ae737a8d06f332f9f7d103"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2020-01-08T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.targets.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.targets.json
new file mode 100644
index 0000000..820691e
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c150e8ed5d352f366a979f4c4b9d556350c414c2da7ef1279045aaed3438c60872142d0dfe5ddbb627fec2d8fb7c5d8e692e04a87230b78d74714c5db035620a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.timestamp.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.timestamp.json
new file mode 100644
index 0000000..aae05fb
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "1d668531c7a0960cf90825faa684106a8aef0799c1b47e72301bac45d87f2dd42c14f1a3ac7db862323ca5177dd4fd686573ea92aea99638f17414dde561c00b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2020-01-02T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "f4ca389c2c9fbc592d91d4e693c31113b8803a11bcb5ecd973581fa0e3d34ce0",
+ "sha512": "92a0989e44c0e9f16d3e56268a3b8dd4e4416ee2ac91a4c871a405f1e426062651ec4effa0078fc4409c8b0422ccad9b1aa197db58f178406f398562b2e98195"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/root.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/root.json
new file mode 100644
index 0000000..0898610
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "d0bf76a5cfc0aee1b8a1b1bf0ed8ca646a1a6d5f205945c515e8546bfd3c1e6b5e07cc0b93836bd030dd05ba68f177aecb05f6bf90c6702fd178e53310022506"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2020-12-31T05:48:20Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/snapshot.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/snapshot.json
new file mode 100644
index 0000000..dcbd2f4
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "61db8765350398f7f750853337d9a55c5d6e790812d29146b5b45d5fd43d2a42c474a7a9fab263c3a50a28114a82f79dbf24ff1f99ae737a8d06f332f9f7d103"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2020-01-08T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/targets.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/targets.json
new file mode 100644
index 0000000..820691e
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c150e8ed5d352f366a979f4c4b9d556350c414c2da7ef1279045aaed3438c60872142d0dfe5ddbb627fec2d8fb7c5d8e692e04a87230b78d74714c5db035620a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/timestamp.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/timestamp.json
new file mode 100644
index 0000000..aae05fb
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/current/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "1d668531c7a0960cf90825faa684106a8aef0799c1b47e72301bac45d87f2dd42c14f1a3ac7db862323ca5177dd4fd686573ea92aea99638f17414dde561c00b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2020-01-02T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "f4ca389c2c9fbc592d91d4e693c31113b8803a11bcb5ecd973581fa0e3d34ce0",
+ "sha512": "92a0989e44c0e9f16d3e56268a3b8dd4e4416ee2ac91a4c871a405f1e426062651ec4effa0078fc4409c8b0422ccad9b1aa197db58f178406f398562b2e98195"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.root.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.root.json
new file mode 100644
index 0000000..0898610
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "d0bf76a5cfc0aee1b8a1b1bf0ed8ca646a1a6d5f205945c515e8546bfd3c1e6b5e07cc0b93836bd030dd05ba68f177aecb05f6bf90c6702fd178e53310022506"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2020-12-31T05:48:20Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.snapshot.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.snapshot.json
new file mode 100644
index 0000000..dcbd2f4
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "61db8765350398f7f750853337d9a55c5d6e790812d29146b5b45d5fd43d2a42c474a7a9fab263c3a50a28114a82f79dbf24ff1f99ae737a8d06f332f9f7d103"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2020-01-08T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.targets.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.targets.json
new file mode 100644
index 0000000..820691e
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c150e8ed5d352f366a979f4c4b9d556350c414c2da7ef1279045aaed3438c60872142d0dfe5ddbb627fec2d8fb7c5d8e692e04a87230b78d74714c5db035620a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.timestamp.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.timestamp.json
new file mode 100644
index 0000000..aae05fb
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "1d668531c7a0960cf90825faa684106a8aef0799c1b47e72301bac45d87f2dd42c14f1a3ac7db862323ca5177dd4fd686573ea92aea99638f17414dde561c00b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2020-01-02T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "f4ca389c2c9fbc592d91d4e693c31113b8803a11bcb5ecd973581fa0e3d34ce0",
+ "sha512": "92a0989e44c0e9f16d3e56268a3b8dd4e4416ee2ac91a4c871a405f1e426062651ec4effa0078fc4409c8b0422ccad9b1aa197db58f178406f398562b2e98195"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/root.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/root.json
new file mode 100644
index 0000000..0898610
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "d0bf76a5cfc0aee1b8a1b1bf0ed8ca646a1a6d5f205945c515e8546bfd3c1e6b5e07cc0b93836bd030dd05ba68f177aecb05f6bf90c6702fd178e53310022506"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2020-12-31T05:48:20Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/snapshot.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/snapshot.json
new file mode 100644
index 0000000..dcbd2f4
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "61db8765350398f7f750853337d9a55c5d6e790812d29146b5b45d5fd43d2a42c474a7a9fab263c3a50a28114a82f79dbf24ff1f99ae737a8d06f332f9f7d103"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2020-01-08T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/targets.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/targets.json
new file mode 100644
index 0000000..820691e
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c150e8ed5d352f366a979f4c4b9d556350c414c2da7ef1279045aaed3438c60872142d0dfe5ddbb627fec2d8fb7c5d8e692e04a87230b78d74714c5db035620a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/timestamp.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/timestamp.json
new file mode 100644
index 0000000..aae05fb
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/client/metadata/previous/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "1d668531c7a0960cf90825faa684106a8aef0799c1b47e72301bac45d87f2dd42c14f1a3ac7db862323ca5177dd4fd686573ea92aea99638f17414dde561c00b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2020-01-02T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "f4ca389c2c9fbc592d91d4e693c31113b8803a11bcb5ecd973581fa0e3d34ce0",
+ "sha512": "92a0989e44c0e9f16d3e56268a3b8dd4e4416ee2ac91a4c871a405f1e426062651ec4effa0078fc4409c8b0422ccad9b1aa197db58f178406f398562b2e98195"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.a.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.a.json
new file mode 100644
index 0000000..90237d1
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.a.json
@@ -0,0 +1,74 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "35eae38d12b845e5e2fb89c957b1e4b16bccfb5717160e0c1a36924b3ae89972d78e0837639e266cf5957ddf9d1f7eb184148ef2d242d7ff99b844342b48020a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": [
+ {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "name": "b",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ },
+ {
+ "keyids": [
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586"
+ ],
+ "name": "e",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ }
+ ]
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "a.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "3f90cedf303207851bbdc5f857e018daf93b4c0083306cef17df547b42e4e985",
+ "sha512": "f4631ef7ea7b015d7b88e411842fafeb78a72f0181bec72ea9754604ede74ea0e491bf8411659aabc96304fc764d0131ce49ba86066ab5f7b7480dde719e0bfd"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.b.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.b.json
new file mode 100644
index 0000000..bee9490
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.b.json
@@ -0,0 +1,74 @@
+{
+ "signatures": [
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "a99aa2bd1aa6473f48fc9c3a93289de4efea45071906bc4deecd3a85ffe0e6bc2c1b1751a2b0975e8f28d1b8114ac964f6dc55e5e160f602d56eff51d25c7907"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": [
+ {
+ "keyids": [
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b"
+ ],
+ "name": "c",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ },
+ {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62"
+ ],
+ "name": "d",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ }
+ ]
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "b.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "949c6a4318dabe8bbd140cef99ea669ba031919ccf9bce0f5b4d0b61d1c0aa2e",
+ "sha512": "f4cc9ce5c73b37e2a6707af7a0ea614ea5fa428bd2509e3af4528a5d330ce98a09c4dd98c859ad9b27b8aba24e1eacbf1af8393fdbfed899cecb995c87a11e3c"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.c.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.c.json
new file mode 100644
index 0000000..9305d64
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.c.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "9f0c5ef7e0a11012256c4a47ed757f9e90e930238f6a6e5c758a014f1b768519acb2b7d9aa9bd0456d71ba09b032598086e42d832830391e307c926c16f5b303"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "c.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "946e2ea9180de673891ae09ce0edfb207966bc32bd6324ebdad2c50c82075ffd",
+ "sha512": "ddb5fb256a368d778b5fdd03d497ad79944c766245f3cccfc8b098b14c488ec424a68b86a6a2add36db4ef0f0214f15dbe0d63fbc5ca7a9619fb4c39544d78a9"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.d.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.d.json
new file mode 100644
index 0000000..2c9f111
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.d.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "04d8264dd6d75fad106d599603801842057de038803935cf9b043fff4d5ee2618675c85291822f18efff770fa89056ed960de35309356a24baef45d9f64c8c0f"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "d.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "b2ce56612fa7e840d56d263f75804f60baaad5d6e0a7d544e65e1791f2df5e55",
+ "sha512": "79b44a183eaa0025f3997b856537502ac1759de74263985ad1dd9ab0bb5cc8ce46b6c45c1bf37adb87c2839cca54ae784c3d3488e994d29f003decc81638d589"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.e.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.e.json
new file mode 100644
index 0000000..a473d7d
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.e.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "11ea2f743a5e5ec7f5bea84804c1fdc2e70f42a1f955668b0072fc6bed00e6535574c129270f97e8849e589a57f48a077903244250ab970eac45f7c46c43b003"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "e.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "027b3d1814dae8547aed3d7e7b99aa0d59d82cc18411037b0ef0318eb754ce86",
+ "sha512": "ffca8c347335e5e019deba8a15212297fe25afc734ac06fcd01b4d9dae12b16d750d7a44bdafcada254148990aa6611bc9ed2f2841da2c0dd70bb870be7275f6"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.f.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.f.json
new file mode 100644
index 0000000..941ae66
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.f.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "ff5f1d3bfc5d31f71997b1c020c06d7c1683e0f7e747cfdbdf6c718f608181b4c81f3212a93ff8e109b377a73af5883e1568257b050a5b6137058a10463f640e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "f.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "f0b07df15d4a3e98ffc6ee6a4c00d04623b0171de458d94dbec4abb7646bc60e",
+ "sha512": "b8061c4c83d478aaadc6758a74861f749420e83dd6bf07e931738b0619404a75d12758429b12f8d047643d6d13047ec0b65bb253cf5f5b961515658864301207"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.root.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.root.json
new file mode 100644
index 0000000..0898610
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "d0bf76a5cfc0aee1b8a1b1bf0ed8ca646a1a6d5f205945c515e8546bfd3c1e6b5e07cc0b93836bd030dd05ba68f177aecb05f6bf90c6702fd178e53310022506"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2020-12-31T05:48:20Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.snapshot.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.snapshot.json
new file mode 100644
index 0000000..dcbd2f4
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.snapshot.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "61db8765350398f7f750853337d9a55c5d6e790812d29146b5b45d5fd43d2a42c474a7a9fab263c3a50a28114a82f79dbf24ff1f99ae737a8d06f332f9f7d103"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2020-01-08T00:00:00Z",
+ "meta": {
+ "targets.json": {
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.targets.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.targets.json
new file mode 100644
index 0000000..820691e
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.targets.json
@@ -0,0 +1,19 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "c150e8ed5d352f366a979f4c4b9d556350c414c2da7ef1279045aaed3438c60872142d0dfe5ddbb627fec2d8fb7c5d8e692e04a87230b78d74714c5db035620a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {},
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.timestamp.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.timestamp.json
new file mode 100644
index 0000000..aae05fb
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/1.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "1d668531c7a0960cf90825faa684106a8aef0799c1b47e72301bac45d87f2dd42c14f1a3ac7db862323ca5177dd4fd686573ea92aea99638f17414dde561c00b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2020-01-02T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "f4ca389c2c9fbc592d91d4e693c31113b8803a11bcb5ecd973581fa0e3d34ce0",
+ "sha512": "92a0989e44c0e9f16d3e56268a3b8dd4e4416ee2ac91a4c871a405f1e426062651ec4effa0078fc4409c8b0422ccad9b1aa197db58f178406f398562b2e98195"
+ },
+ "length": 431,
+ "version": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.root.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.root.json
new file mode 100644
index 0000000..2d5222c
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e911db300e8eb378a2cabc48979e4c8f72beca30335db0579a7a1d81ae90c8bc63c27d92ce5a17846e3b1adb63a45a411bb4308e018e0d3cc4c9908311b5f208"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2020-12-31T05:48:20Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.snapshot.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.snapshot.json
new file mode 100644
index 0000000..11e7e07
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.snapshot.json
@@ -0,0 +1,37 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "a12c5320e165f6a674b5d030c1c2f6a1716ad413ad6bda8942febab0af19f510958998d36c76bb47274fe20d3531f67f99f6f6dde8bc3bc0bca7098d8042510c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2020-01-08T00:00:00Z",
+ "meta": {
+ "a.json": {
+ "version": 1
+ },
+ "b.json": {
+ "version": 1
+ },
+ "c.json": {
+ "version": 1
+ },
+ "d.json": {
+ "version": 1
+ },
+ "e.json": {
+ "version": 1
+ },
+ "f.json": {
+ "version": 1
+ },
+ "targets.json": {
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.targets.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.targets.json
new file mode 100644
index 0000000..bece42a
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.targets.json
@@ -0,0 +1,73 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "91abfb44e844a293837d1e081953acc4a7d179ff695353ea0472eb71846f8aee9903b352fa595084243218bec93336defc184ed0b4a1bc50e497878ed7f03c0c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": [
+ {
+ "keyids": [
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "name": "a",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ },
+ {
+ "keyids": [
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be"
+ ],
+ "name": "f",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ }
+ ]
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "targets.txt": {
+ "hashes": {
+ "sha256": "6da6f624811b0d7792c57dc9ed58f1af8cbf4ebfaaaa3ea2011b57b4af63f7e6",
+ "sha512": "0951c719b5adf26a55700bd1ac6b657fb513b04e4adb335a935a0da7f12f2ac99834b47442d0b832e5f4ffdd365ef538f14d3a52221c0cf81ef7b68ce312476a"
+ },
+ "length": 21
+ }
+ },
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.timestamp.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.timestamp.json
new file mode 100644
index 0000000..218e6de
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/2.timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c48177d304312637216cfa3e424e1f7a214dfbd2121597e2b6ef10b40c000d48720078742503d68d19e50dd39da093e5d25d72ff6a634e7e8880bd9992fc110b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2020-01-02T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "b5a9218242238080aa294948c18f9d48022b1c3ca68ebdde92fed68bbc0c3c09",
+ "sha512": "ffb8446b2517a5db4a0ca7b59f4800bcd00f807e918b884af53c7c82b941df242d790e64feaba08ae33edd92902c494f85c9f7c25e8b7071ab0f0b8a1e61206d"
+ },
+ "length": 659,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/a.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/a.json
new file mode 100644
index 0000000..90237d1
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/a.json
@@ -0,0 +1,74 @@
+{
+ "signatures": [
+ {
+ "keyid": "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b",
+ "sig": "35eae38d12b845e5e2fb89c957b1e4b16bccfb5717160e0c1a36924b3ae89972d78e0837639e266cf5957ddf9d1f7eb184148ef2d242d7ff99b844342b48020a"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e104199cda6e018d7d9044fa6225aa5dc9c2af5ee4e1c0fe6d16ad002220390d"
+ },
+ "scheme": "ed25519"
+ },
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "82f52e4503dbb364fabe8e5567f1cf909d4175d45468a021dfe75653db9ac98c"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": [
+ {
+ "keyids": [
+ "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68"
+ ],
+ "name": "b",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ },
+ {
+ "keyids": [
+ "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586"
+ ],
+ "name": "e",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ }
+ ]
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "a.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "3f90cedf303207851bbdc5f857e018daf93b4c0083306cef17df547b42e4e985",
+ "sha512": "f4631ef7ea7b015d7b88e411842fafeb78a72f0181bec72ea9754604ede74ea0e491bf8411659aabc96304fc764d0131ce49ba86066ab5f7b7480dde719e0bfd"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/b.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/b.json
new file mode 100644
index 0000000..bee9490
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/b.json
@@ -0,0 +1,74 @@
+{
+ "signatures": [
+ {
+ "keyid": "718fedad390b4d0d470b890781eb8c94e5a7e975aebe65fc0862246c945fce68",
+ "sig": "a99aa2bd1aa6473f48fc9c3a93289de4efea45071906bc4deecd3a85ffe0e6bc2c1b1751a2b0975e8f28d1b8114ac964f6dc55e5e160f602d56eff51d25c7907"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "06e4dee0de7826c8d539a6112940b7459892b4ecaf696e67dc064aea0923f95c"
+ },
+ "scheme": "ed25519"
+ },
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "fa386632ae9cc358ad0b56565edef362ad10d7fadb05bc8dc8995627372b990e"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": [
+ {
+ "keyids": [
+ "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b"
+ ],
+ "name": "c",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ },
+ {
+ "keyids": [
+ "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62"
+ ],
+ "name": "d",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ }
+ ]
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "b.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "949c6a4318dabe8bbd140cef99ea669ba031919ccf9bce0f5b4d0b61d1c0aa2e",
+ "sha512": "f4cc9ce5c73b37e2a6707af7a0ea614ea5fa428bd2509e3af4528a5d330ce98a09c4dd98c859ad9b27b8aba24e1eacbf1af8393fdbfed899cecb995c87a11e3c"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/c.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/c.json
new file mode 100644
index 0000000..9305d64
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/c.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "9ca81f7ff17f6218246474a51b47eb035741bc472557ef5ac493e279f446b85b",
+ "sig": "9f0c5ef7e0a11012256c4a47ed757f9e90e930238f6a6e5c758a014f1b768519acb2b7d9aa9bd0456d71ba09b032598086e42d832830391e307c926c16f5b303"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "c.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "946e2ea9180de673891ae09ce0edfb207966bc32bd6324ebdad2c50c82075ffd",
+ "sha512": "ddb5fb256a368d778b5fdd03d497ad79944c766245f3cccfc8b098b14c488ec424a68b86a6a2add36db4ef0f0214f15dbe0d63fbc5ca7a9619fb4c39544d78a9"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/d.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/d.json
new file mode 100644
index 0000000..2c9f111
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/d.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "e9829d3f2fdff6d6f31002c17cf7f20cf0398e215ca0c0c44d075ccd76a26f62",
+ "sig": "04d8264dd6d75fad106d599603801842057de038803935cf9b043fff4d5ee2618675c85291822f18efff770fa89056ed960de35309356a24baef45d9f64c8c0f"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "d.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "b2ce56612fa7e840d56d263f75804f60baaad5d6e0a7d544e65e1791f2df5e55",
+ "sha512": "79b44a183eaa0025f3997b856537502ac1759de74263985ad1dd9ab0bb5cc8ce46b6c45c1bf37adb87c2839cca54ae784c3d3488e994d29f003decc81638d589"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/e.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/e.json
new file mode 100644
index 0000000..a473d7d
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/e.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "15024498e03f033ec92758a1dc7107b34eebe759b09827b02a7fb3c64ca3e586",
+ "sig": "11ea2f743a5e5ec7f5bea84804c1fdc2e70f42a1f955668b0072fc6bed00e6535574c129270f97e8849e589a57f48a077903244250ab970eac45f7c46c43b003"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "e.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "027b3d1814dae8547aed3d7e7b99aa0d59d82cc18411037b0ef0318eb754ce86",
+ "sha512": "ffca8c347335e5e019deba8a15212297fe25afc734ac06fcd01b4d9dae12b16d750d7a44bdafcada254148990aa6611bc9ed2f2841da2c0dd70bb870be7275f6"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/f.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/f.json
new file mode 100644
index 0000000..941ae66
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/f.json
@@ -0,0 +1,28 @@
+{
+ "signatures": [
+ {
+ "keyid": "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be",
+ "sig": "ff5f1d3bfc5d31f71997b1c020c06d7c1683e0f7e747cfdbdf6c718f608181b4c81f3212a93ff8e109b377a73af5883e1568257b050a5b6137058a10463f640e"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {},
+ "roles": []
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "f.txt": {
+ "custom": {},
+ "hashes": {
+ "sha256": "f0b07df15d4a3e98ffc6ee6a4c00d04623b0171de458d94dbec4abb7646bc60e",
+ "sha512": "b8061c4c83d478aaadc6758a74861f749420e83dd6bf07e931738b0619404a75d12758429b12f8d047643d6d13047ec0b65bb253cf5f5b961515658864301207"
+ },
+ "length": 15
+ }
+ },
+ "version": 1
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/root.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/root.json
new file mode 100644
index 0000000..2d5222c
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/root.json
@@ -0,0 +1,87 @@
+{
+ "signatures": [
+ {
+ "keyid": "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129",
+ "sig": "e911db300e8eb378a2cabc48979e4c8f72beca30335db0579a7a1d81ae90c8bc63c27d92ce5a17846e3b1adb63a45a411bb4308e018e0d3cc4c9908311b5f208"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": true,
+ "expires": "2020-12-31T05:48:20Z",
+ "keys": {
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6bac59b8d9e1aae02fae6fba6e7fe3fc9fe5b4a9fe98c3fca255d8c8ec3e5b35"
+ },
+ "scheme": "ed25519"
+ },
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "6400d770c7c1bce4b3d59ce0079ed686e843b6500bbea77d869a1ae7df4565a1"
+ },
+ "scheme": "ed25519"
+ },
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "28bf74baa87ed923f8fa27e3292684f8ec4730ce0bdc65150ed58199206ce089"
+ },
+ "scheme": "ed25519"
+ },
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "e6ae9d3b67d7b3ce274130291dd90287f32b8fd72bfb4ac5430859ebd1c28a46"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "d4dab4b4d68b91665a6d0dac5b4e64677aa6d853fc787669168b4b4ba9822129"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/snapshot.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/snapshot.json
new file mode 100644
index 0000000..11e7e07
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/snapshot.json
@@ -0,0 +1,37 @@
+{
+ "signatures": [
+ {
+ "keyid": "77dfdca206c0fe1b8e55d67d21dd0e195a0998a9d2b56c6d3ee8f68d04c21e93",
+ "sig": "a12c5320e165f6a674b5d030c1c2f6a1716ad413ad6bda8942febab0af19f510958998d36c76bb47274fe20d3531f67f99f6f6dde8bc3bc0bca7098d8042510c"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2020-01-08T00:00:00Z",
+ "meta": {
+ "a.json": {
+ "version": 1
+ },
+ "b.json": {
+ "version": 1
+ },
+ "c.json": {
+ "version": 1
+ },
+ "d.json": {
+ "version": 1
+ },
+ "e.json": {
+ "version": 1
+ },
+ "f.json": {
+ "version": 1
+ },
+ "targets.json": {
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/targets.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/targets.json
new file mode 100644
index 0000000..bece42a
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/targets.json
@@ -0,0 +1,73 @@
+{
+ "signatures": [
+ {
+ "keyid": "e4dae3872d28d29f7624a702bfd25f68453544d597229ee9e0a8569d1f940cf4",
+ "sig": "91abfb44e844a293837d1e081953acc4a7d179ff695353ea0472eb71846f8aee9903b352fa595084243218bec93336defc184ed0b4a1bc50e497878ed7f03c0c"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "f758af464295e62a1da4d3267be6d13f4aba9c7d52166d01b6bd5b4559496c9d"
+ },
+ "scheme": "ed25519"
+ },
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ed25519",
+ "keyval": {
+ "public": "683d345a948a9baa343be4e44c076ca115da3838e72c28a06340c8ec1b3ef6be"
+ },
+ "scheme": "ed25519"
+ }
+ },
+ "roles": [
+ {
+ "keyids": [
+ "05e17c1501d627b2597322f80d33aacec6f30a507552d3326a88913422b0e30b"
+ ],
+ "name": "a",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ },
+ {
+ "keyids": [
+ "f581c9aeff9989106aeeea35319d1d1f067149619a2ff005249d6f60560557be"
+ ],
+ "name": "f",
+ "paths": [
+ "*.txt"
+ ],
+ "terminating": false,
+ "threshold": 1
+ }
+ ]
+ },
+ "expires": "2020-04-01T07:27:10Z",
+ "spec_version": "1.0.0",
+ "targets": {
+ "targets.txt": {
+ "hashes": {
+ "sha256": "6da6f624811b0d7792c57dc9ed58f1af8cbf4ebfaaaa3ea2011b57b4af63f7e6",
+ "sha512": "0951c719b5adf26a55700bd1ac6b657fb513b04e4adb335a935a0da7f12f2ac99834b47442d0b832e5f4ffdd365ef538f14d3a52221c0cf81ef7b68ce312476a"
+ },
+ "length": 21
+ }
+ },
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/timestamp.json b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/timestamp.json
new file mode 100644
index 0000000..218e6de
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/metadata/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "3a05831328273e4b821c3bbe1fed0c5332749d8e071675879af26a401a5c85ae",
+ "sig": "c48177d304312637216cfa3e424e1f7a214dfbd2121597e2b6ef10b40c000d48720078742503d68d19e50dd39da093e5d25d72ff6a634e7e8880bd9992fc110b"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2020-01-02T00:00:00Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "b5a9218242238080aa294948c18f9d48022b1c3ca68ebdde92fed68bbc0c3c09",
+ "sha512": "ffb8446b2517a5db4a0ca7b59f4800bcd00f807e918b884af53c7c82b941df242d790e64feaba08ae33edd92902c494f85c9f7c25e8b7071ab0f0b8a1e61206d"
+ },
+ "length": 659,
+ "version": 2
+ }
+ },
+ "spec_version": "1.0.0",
+ "version": 2
+ }
+} \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/027b3d1814dae8547aed3d7e7b99aa0d59d82cc18411037b0ef0318eb754ce86.e.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/027b3d1814dae8547aed3d7e7b99aa0d59d82cc18411037b0ef0318eb754ce86.e.txt
new file mode 100644
index 0000000..7bece39
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/027b3d1814dae8547aed3d7e7b99aa0d59d82cc18411037b0ef0318eb754ce86.e.txt
@@ -0,0 +1 @@
+Contents: e.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/0951c719b5adf26a55700bd1ac6b657fb513b04e4adb335a935a0da7f12f2ac99834b47442d0b832e5f4ffdd365ef538f14d3a52221c0cf81ef7b68ce312476a.targets.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/0951c719b5adf26a55700bd1ac6b657fb513b04e4adb335a935a0da7f12f2ac99834b47442d0b832e5f4ffdd365ef538f14d3a52221c0cf81ef7b68ce312476a.targets.txt
new file mode 100644
index 0000000..5f1e603
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/0951c719b5adf26a55700bd1ac6b657fb513b04e4adb335a935a0da7f12f2ac99834b47442d0b832e5f4ffdd365ef538f14d3a52221c0cf81ef7b68ce312476a.targets.txt
@@ -0,0 +1 @@
+Contents: targets.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/3f90cedf303207851bbdc5f857e018daf93b4c0083306cef17df547b42e4e985.a.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/3f90cedf303207851bbdc5f857e018daf93b4c0083306cef17df547b42e4e985.a.txt
new file mode 100644
index 0000000..3c40561
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/3f90cedf303207851bbdc5f857e018daf93b4c0083306cef17df547b42e4e985.a.txt
@@ -0,0 +1 @@
+Contents: a.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/6da6f624811b0d7792c57dc9ed58f1af8cbf4ebfaaaa3ea2011b57b4af63f7e6.targets.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/6da6f624811b0d7792c57dc9ed58f1af8cbf4ebfaaaa3ea2011b57b4af63f7e6.targets.txt
new file mode 100644
index 0000000..5f1e603
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/6da6f624811b0d7792c57dc9ed58f1af8cbf4ebfaaaa3ea2011b57b4af63f7e6.targets.txt
@@ -0,0 +1 @@
+Contents: targets.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/79b44a183eaa0025f3997b856537502ac1759de74263985ad1dd9ab0bb5cc8ce46b6c45c1bf37adb87c2839cca54ae784c3d3488e994d29f003decc81638d589.d.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/79b44a183eaa0025f3997b856537502ac1759de74263985ad1dd9ab0bb5cc8ce46b6c45c1bf37adb87c2839cca54ae784c3d3488e994d29f003decc81638d589.d.txt
new file mode 100644
index 0000000..36ce6ad
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/79b44a183eaa0025f3997b856537502ac1759de74263985ad1dd9ab0bb5cc8ce46b6c45c1bf37adb87c2839cca54ae784c3d3488e994d29f003decc81638d589.d.txt
@@ -0,0 +1 @@
+Contents: d.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/946e2ea9180de673891ae09ce0edfb207966bc32bd6324ebdad2c50c82075ffd.c.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/946e2ea9180de673891ae09ce0edfb207966bc32bd6324ebdad2c50c82075ffd.c.txt
new file mode 100644
index 0000000..924df50
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/946e2ea9180de673891ae09ce0edfb207966bc32bd6324ebdad2c50c82075ffd.c.txt
@@ -0,0 +1 @@
+Contents: c.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/949c6a4318dabe8bbd140cef99ea669ba031919ccf9bce0f5b4d0b61d1c0aa2e.b.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/949c6a4318dabe8bbd140cef99ea669ba031919ccf9bce0f5b4d0b61d1c0aa2e.b.txt
new file mode 100644
index 0000000..976e178
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/949c6a4318dabe8bbd140cef99ea669ba031919ccf9bce0f5b4d0b61d1c0aa2e.b.txt
@@ -0,0 +1 @@
+Contents: b.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/a.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/a.txt
new file mode 100644
index 0000000..3c40561
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/a.txt
@@ -0,0 +1 @@
+Contents: a.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b.txt
new file mode 100644
index 0000000..976e178
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b.txt
@@ -0,0 +1 @@
+Contents: b.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b2ce56612fa7e840d56d263f75804f60baaad5d6e0a7d544e65e1791f2df5e55.d.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b2ce56612fa7e840d56d263f75804f60baaad5d6e0a7d544e65e1791f2df5e55.d.txt
new file mode 100644
index 0000000..36ce6ad
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b2ce56612fa7e840d56d263f75804f60baaad5d6e0a7d544e65e1791f2df5e55.d.txt
@@ -0,0 +1 @@
+Contents: d.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b8061c4c83d478aaadc6758a74861f749420e83dd6bf07e931738b0619404a75d12758429b12f8d047643d6d13047ec0b65bb253cf5f5b961515658864301207.f.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b8061c4c83d478aaadc6758a74861f749420e83dd6bf07e931738b0619404a75d12758429b12f8d047643d6d13047ec0b65bb253cf5f5b961515658864301207.f.txt
new file mode 100644
index 0000000..f7c5df5
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/b8061c4c83d478aaadc6758a74861f749420e83dd6bf07e931738b0619404a75d12758429b12f8d047643d6d13047ec0b65bb253cf5f5b961515658864301207.f.txt
@@ -0,0 +1 @@
+Contents: f.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/c.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/c.txt
new file mode 100644
index 0000000..924df50
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/c.txt
@@ -0,0 +1 @@
+Contents: c.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/d.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/d.txt
new file mode 100644
index 0000000..36ce6ad
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/d.txt
@@ -0,0 +1 @@
+Contents: d.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/ddb5fb256a368d778b5fdd03d497ad79944c766245f3cccfc8b098b14c488ec424a68b86a6a2add36db4ef0f0214f15dbe0d63fbc5ca7a9619fb4c39544d78a9.c.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/ddb5fb256a368d778b5fdd03d497ad79944c766245f3cccfc8b098b14c488ec424a68b86a6a2add36db4ef0f0214f15dbe0d63fbc5ca7a9619fb4c39544d78a9.c.txt
new file mode 100644
index 0000000..924df50
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/ddb5fb256a368d778b5fdd03d497ad79944c766245f3cccfc8b098b14c488ec424a68b86a6a2add36db4ef0f0214f15dbe0d63fbc5ca7a9619fb4c39544d78a9.c.txt
@@ -0,0 +1 @@
+Contents: c.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/e.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/e.txt
new file mode 100644
index 0000000..7bece39
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/e.txt
@@ -0,0 +1 @@
+Contents: e.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f.txt
new file mode 100644
index 0000000..f7c5df5
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f.txt
@@ -0,0 +1 @@
+Contents: f.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f0b07df15d4a3e98ffc6ee6a4c00d04623b0171de458d94dbec4abb7646bc60e.f.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f0b07df15d4a3e98ffc6ee6a4c00d04623b0171de458d94dbec4abb7646bc60e.f.txt
new file mode 100644
index 0000000..f7c5df5
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f0b07df15d4a3e98ffc6ee6a4c00d04623b0171de458d94dbec4abb7646bc60e.f.txt
@@ -0,0 +1 @@
+Contents: f.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f4631ef7ea7b015d7b88e411842fafeb78a72f0181bec72ea9754604ede74ea0e491bf8411659aabc96304fc764d0131ce49ba86066ab5f7b7480dde719e0bfd.a.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f4631ef7ea7b015d7b88e411842fafeb78a72f0181bec72ea9754604ede74ea0e491bf8411659aabc96304fc764d0131ce49ba86066ab5f7b7480dde719e0bfd.a.txt
new file mode 100644
index 0000000..3c40561
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f4631ef7ea7b015d7b88e411842fafeb78a72f0181bec72ea9754604ede74ea0e491bf8411659aabc96304fc764d0131ce49ba86066ab5f7b7480dde719e0bfd.a.txt
@@ -0,0 +1 @@
+Contents: a.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f4cc9ce5c73b37e2a6707af7a0ea614ea5fa428bd2509e3af4528a5d330ce98a09c4dd98c859ad9b27b8aba24e1eacbf1af8393fdbfed899cecb995c87a11e3c.b.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f4cc9ce5c73b37e2a6707af7a0ea614ea5fa428bd2509e3af4528a5d330ce98a09c4dd98c859ad9b27b8aba24e1eacbf1af8393fdbfed899cecb995c87a11e3c.b.txt
new file mode 100644
index 0000000..976e178
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/f4cc9ce5c73b37e2a6707af7a0ea614ea5fa428bd2509e3af4528a5d330ce98a09c4dd98c859ad9b27b8aba24e1eacbf1af8393fdbfed899cecb995c87a11e3c.b.txt
@@ -0,0 +1 @@
+Contents: b.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/ffca8c347335e5e019deba8a15212297fe25afc734ac06fcd01b4d9dae12b16d750d7a44bdafcada254148990aa6611bc9ed2f2841da2c0dd70bb870be7275f6.e.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/ffca8c347335e5e019deba8a15212297fe25afc734ac06fcd01b4d9dae12b16d750d7a44bdafcada254148990aa6611bc9ed2f2841da2c0dd70bb870be7275f6.e.txt
new file mode 100644
index 0000000..7bece39
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/ffca8c347335e5e019deba8a15212297fe25afc734ac06fcd01b4d9dae12b16d750d7a44bdafcada254148990aa6611bc9ed2f2841da2c0dd70bb870be7275f6.e.txt
@@ -0,0 +1 @@
+Contents: e.txt \ No newline at end of file
diff --git a/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/targets.txt b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/targets.txt
new file mode 100644
index 0000000..5f1e603
--- /dev/null
+++ b/client/testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation/server/targets/targets.txt
@@ -0,0 +1 @@
+Contents: targets.txt \ No newline at end of file
diff --git a/client/testdata/tools/gen-keys.go b/client/testdata/tools/gen-keys.go
new file mode 100644
index 0000000..f4db562
--- /dev/null
+++ b/client/testdata/tools/gen-keys.go
@@ -0,0 +1,50 @@
+// This helper files generates a bunch of ed25519 keys to be used by the test
+// runners. This is done such that the signatures stay stable when the metadata
+// is regenerated.
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+var expirationDate = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
+
+func main() {
+ rolenames := []string{
+ "root",
+ "snapshot",
+ "targets",
+ "timestamp",
+ }
+
+ roles := make(map[string][][]*data.PrivateKey)
+
+ for _, name := range rolenames {
+ keys := [][]*data.PrivateKey{}
+
+ for i := 0; i < 2; i++ {
+ signer, err := keys.GenerateEd25519Key()
+ assertNoError(err)
+ keys = append(keys, []*data.PrivateKey{signer})
+ }
+
+ roles[name] = keys
+ }
+
+ s, err := json.MarshalIndent(&roles, "", " ")
+ assertNoError(err)
+
+ os.WriteFile("keys.json", []byte(s), 0644)
+}
+
+func assertNoError(err error) {
+ if err != nil {
+ panic(fmt.Sprintf("assertion failed: %s", err))
+ }
+}
diff --git a/client/testdata/tools/linkify-metadata.go b/client/testdata/tools/linkify-metadata.go
new file mode 100644
index 0000000..1788680
--- /dev/null
+++ b/client/testdata/tools/linkify-metadata.go
@@ -0,0 +1,112 @@
+// This helper command identifies duplicated metadata across multiple test
+// stages, and replaces them with symlinks in order to make changes to them
+// easier to read.
+
+package main
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+)
+
+func main() {
+ for _, consistentSnapshot := range []bool{false, true} {
+ err := linkifyDir(fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot))
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+}
+
+func linkifyDir(rootDir string) error {
+ stepDirs, err := readStepDirs(rootDir)
+ if err != nil {
+ return err
+ }
+
+ oldDir := stepDirs[0]
+ oldHashes := computeHashes(oldDir)
+
+ for _, dir := range stepDirs[1:] {
+ log.Printf("checking: %s", dir)
+
+ hashes := computeHashes(dir)
+
+ for path, hash := range hashes {
+ if oldHashes[path] == hash {
+ newPath := filepath.Join(dir, path)
+ oldPath := filepath.Join(oldDir, path)
+ if err = linkifyPath(oldPath, newPath); err != nil {
+ return err
+ }
+ }
+ }
+
+ oldDir = dir
+ oldHashes = hashes
+ log.Printf("-----")
+ }
+
+ return nil
+}
+
+func readStepDirs(rootDir string) ([]string, error) {
+ dirEntries, err := os.ReadDir(rootDir)
+ if err != nil {
+ return []string{}, err
+ }
+
+ // We only want to consider linkifying directories.
+ var dirs []string
+ for _, dirEntry := range dirEntries {
+ if dirEntry.IsDir() {
+ dirs = append(dirs, filepath.Join(rootDir, dirEntry.Name()))
+ }
+ }
+
+ return dirs, nil
+}
+
+func computeHashes(dir string) map[string][32]byte {
+ hashes := make(map[string][32]byte)
+
+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if info.IsDir() || !info.Mode().IsRegular() {
+ return nil
+ }
+
+ bytes, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ hashes[path[len(dir)+1:]] = sha256.Sum256(bytes)
+
+ return nil
+ })
+ if err != nil {
+ log.Fatalf("failed to linkify: %s", err)
+ }
+
+ return hashes
+}
+
+func linkifyPath(oldPath string, newPath string) error {
+ p, err := filepath.Rel(filepath.Dir(newPath), oldPath)
+ if err != nil {
+ return err
+ }
+ log.Printf("symlinking %s to %s", newPath, p)
+
+ if err = os.Remove(newPath); err != nil {
+ return err
+ }
+ if err = os.Symlink(p, newPath); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/cmd/tuf-client/README.md b/cmd/tuf-client/README.md
new file mode 100644
index 0000000..1fe8038
--- /dev/null
+++ b/cmd/tuf-client/README.md
@@ -0,0 +1,48 @@
+# go-tuf client CLI
+
+## Install
+
+```
+go get github.com/theupdateframework/go-tuf/cmd/tuf-client
+```
+
+## Usage
+
+The CLI provides three commands:
+
+* `tuf-client init` - initialize a local file store using root keys (e.g. from
+ the output of `tuf root-keys`)
+* `tuf-client list` - list available targets and their file sizes
+* `tuf-client get` - get a target file and write to STDOUT
+
+All commands require the base URL of the TUF repository as the first non-flag
+argument, and accept an optional `--store` flag which is the path to the local
+storage.
+
+Run `tuf-client help` from the command line to get more detailed usage
+information.
+
+## Examples
+
+```
+# init
+$ tuf-client init https://example.com/path/to/repo
+
+# init with a custom store path
+$ tuf-client init --store /tmp/tuf.db https://example.com/path/to/repo
+
+# list available targets
+$ tuf-client list https://example.com/path/to/repo
+PATH SIZE
+/foo.txt 1.6KB
+/bar.txt 336B
+/baz.txt 1.5KB
+
+# get a target
+$ tuf-client get https://example.com/path/to/repo /foo.txt
+the contents of foo.txt
+
+# the prefixed / is optional
+$ tuf-client get https://example.com/path/to/repo foo.txt
+the contents of foo.txt
+```
diff --git a/cmd/tuf-client/get.go b/cmd/tuf-client/get.go
new file mode 100644
index 0000000..60df646
--- /dev/null
+++ b/cmd/tuf-client/get.go
@@ -0,0 +1,51 @@
+package main
+
+import (
+ "io"
+ "os"
+
+ "github.com/flynn/go-docopt"
+ tuf "github.com/theupdateframework/go-tuf/client"
+ "github.com/theupdateframework/go-tuf/util"
+)
+
+func init() {
+ register("get", cmdGet, `
+usage: tuf-client get [-s|--store=<path>] <url> <target>
+
+Options:
+ -s <path> The path to the local file store [default: tuf.db]
+
+Get a target from the repository.
+ `)
+}
+
+type tmpFile struct {
+ *os.File
+}
+
+func (t *tmpFile) Delete() error {
+ t.Close()
+ return os.Remove(t.Name())
+}
+
+func cmdGet(args *docopt.Args, client *tuf.Client) error {
+ if _, err := client.Update(); err != nil {
+ return err
+ }
+ target := util.NormalizeTarget(args.String["<target>"])
+ file, err := os.CreateTemp("", "go-tuf")
+ if err != nil {
+ return err
+ }
+ tmp := tmpFile{file}
+ if err := client.Download(target, &tmp); err != nil {
+ return err
+ }
+ defer tmp.Delete()
+ if _, err := tmp.Seek(0, io.SeekStart); err != nil {
+ return err
+ }
+ _, err = io.Copy(os.Stdout, file)
+ return err
+}
diff --git a/cmd/tuf-client/init.go b/cmd/tuf-client/init.go
new file mode 100644
index 0000000..b3a0f6a
--- /dev/null
+++ b/cmd/tuf-client/init.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "io"
+ "os"
+
+ "github.com/flynn/go-docopt"
+ tuf "github.com/theupdateframework/go-tuf/client"
+)
+
+func init() {
+ register("init", cmdInit, `
+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 metadata.
+ `)
+}
+
+func cmdInit(args *docopt.Args, client *tuf.Client) error {
+ file := args.String["<root-metadata-file>"]
+ var in io.Reader
+ if file == "" || file == "-" {
+ in = os.Stdin
+ } else {
+ var err error
+ in, err = os.Open(file)
+ if err != nil {
+ return err
+ }
+ }
+ bytes, err := io.ReadAll(in)
+ if err != nil {
+ return err
+ }
+ return client.Init(bytes)
+}
diff --git a/cmd/tuf-client/list.go b/cmd/tuf-client/list.go
new file mode 100644
index 0000000..f00209d
--- /dev/null
+++ b/cmd/tuf-client/list.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "text/tabwriter"
+
+ "github.com/dustin/go-humanize"
+ "github.com/flynn/go-docopt"
+ tuf "github.com/theupdateframework/go-tuf/client"
+)
+
+func init() {
+ register("list", cmdList, `
+usage: tuf-client list [-s|--store=<path>] <url>
+
+Options:
+ -s <path> The path to the local file store [default: tuf.db]
+
+List available target files.
+ `)
+}
+
+func cmdList(args *docopt.Args, client *tuf.Client) error {
+ if _, err := client.Update(); err != nil {
+ return err
+ }
+ targets, err := client.Targets()
+ if err != nil {
+ return err
+ }
+ w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
+ defer w.Flush()
+ fmt.Fprintln(w, "PATH\tSIZE")
+ for path, meta := range targets {
+ fmt.Fprintf(w, "%s\t%s\n", path, humanize.Bytes(uint64(meta.Length)))
+ }
+ return nil
+}
diff --git a/cmd/tuf-client/main.go b/cmd/tuf-client/main.go
new file mode 100644
index 0000000..df3632b
--- /dev/null
+++ b/cmd/tuf-client/main.go
@@ -0,0 +1,98 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+
+ docopt "github.com/flynn/go-docopt"
+ tuf "github.com/theupdateframework/go-tuf/client"
+ tuf_leveldbstore "github.com/theupdateframework/go-tuf/client/leveldbstore"
+)
+
+func main() {
+ log.SetFlags(0)
+
+ usage := `usage: tuf-client [-h|--help] <command> [<args>...]
+
+Options:
+ -h, --help
+
+Commands:
+ help Show usage for a specific command
+ init Initialize with root keys
+ list List available target files
+ get Get a target file
+
+See "tuf-client help <command>" for more information on a specific command.
+`
+
+ args, _ := docopt.Parse(usage, nil, true, "", true)
+ cmd := args.String["<command>"]
+ cmdArgs := args.All["<args>"].([]string)
+
+ if cmd == "help" {
+ if len(cmdArgs) == 0 { // `tuf-client help`
+ fmt.Fprint(os.Stdout, usage)
+ return
+ } else { // `tuf-client help <command>`
+ cmd = cmdArgs[0]
+ cmdArgs = []string{"--help"}
+ }
+ }
+
+ if err := runCommand(cmd, cmdArgs); err != nil {
+ log.Fatalln("ERROR:", err)
+ }
+}
+
+type cmdFunc func(*docopt.Args, *tuf.Client) error
+
+type command struct {
+ usage string
+ f cmdFunc
+}
+
+var commands = make(map[string]*command)
+
+func register(name string, f cmdFunc, usage string) {
+ commands[name] = &command{usage: usage, f: f}
+}
+
+func runCommand(name string, args []string) error {
+ argv := make([]string, 1, 1+len(args))
+ argv[0] = name
+ argv = append(argv, args...)
+
+ cmd, ok := commands[name]
+ if !ok {
+ return fmt.Errorf("%s is not a tuf-client command. See 'tuf-client help'", name)
+ }
+
+ parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", true)
+ if err != nil {
+ return err
+ }
+
+ client, err := tufClient(parsedArgs)
+ if err != nil {
+ return err
+ }
+ return cmd.f(parsedArgs, client)
+}
+
+func tufClient(args *docopt.Args) (*tuf.Client, error) {
+ store, ok := args.String["--store"]
+ if !ok {
+ store = args.String["-s"]
+ }
+ local, err := tuf_leveldbstore.FileLocalStore(store)
+ if err != nil {
+ return nil, err
+ }
+ remote, err := tuf.HTTPRemoteStore(args.String["<url>"], nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ return tuf.NewClient(local, remote), nil
+}
diff --git a/cmd/tuf/add.go b/cmd/tuf/add.go
new file mode 100644
index 0000000..39dd0a1
--- /dev/null
+++ b/cmd/tuf/add.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "encoding/json"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("add", cmdAdd, `
+usage: tuf add [--expires=<days>] [--custom=<data>] [<path>...]
+
+Add target file(s).
+
+Alternatively, passphrases can be set via environment variables in the
+form of TUF_{{ROLE}}_PASSPHRASE
+
+Options:
+ --expires=<days> Set the targets metadata file to expire <days> days from now.
+ --custom=<data> Set custom JSON data for the target(s).
+`)
+}
+
+func cmdAdd(args *docopt.Args, repo *tuf.Repo) error {
+ var custom json.RawMessage
+ if c := args.String["--custom"]; c != "" {
+ custom = json.RawMessage(c)
+ }
+ paths := args.All["<path>"].([]string)
+ if arg := args.String["--expires"]; arg != "" {
+ expires, err := parseExpires(arg)
+ if err != nil {
+ return err
+ }
+ return repo.AddTargetsWithExpires(paths, custom, expires)
+ }
+ return repo.AddTargets(paths, custom)
+}
diff --git a/cmd/tuf/add_signatures.go b/cmd/tuf/add_signatures.go
new file mode 100644
index 0000000..6508736
--- /dev/null
+++ b/cmd/tuf/add_signatures.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+func init() {
+ register("add-signatures", cmdAddSignature, `
+usage: tuf add-signatures --signatures <sig_file> <metadata>
+
+Adds signatures (the output of "sign-payload") to the given role metadata file.
+
+If the signature does not verify, it will not be added.
+`)
+}
+
+func cmdAddSignature(args *docopt.Args, repo *tuf.Repo) error {
+ roleFilename := args.String["<metadata>"]
+
+ f := args.String["<sig_file>"]
+ sigBytes, err := os.ReadFile(f)
+ if err != nil {
+ return err
+ }
+ sigs := []data.Signature{}
+ if err = json.Unmarshal(sigBytes, &sigs); err != nil {
+ return err
+ }
+ for _, sig := range sigs {
+ if err = repo.AddOrUpdateSignature(roleFilename, sig); err != nil {
+ return err
+ }
+ }
+ fmt.Fprintln(os.Stderr, "tuf: added", len(sigs), "new signature(s)")
+ return nil
+}
diff --git a/cmd/tuf/change_passphrase.go b/cmd/tuf/change_passphrase.go
new file mode 100644
index 0000000..be685ad
--- /dev/null
+++ b/cmd/tuf/change_passphrase.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("change-passphrase", cmdChangePassphrase, `
+usage: tuf change-passphrase <role>
+
+Changes the passphrase for given role keys file.
+
+Alternatively, passphrases can be passed via environment variables in the
+form of TUF_{{ROLE}}_PASSPHRASE for existing ones and
+TUF_NEW_{{ROLE}}_PASSPHRASE for setting new ones.
+`)
+}
+
+func cmdChangePassphrase(args *docopt.Args, repo *tuf.Repo) error {
+ return repo.ChangePassphrase(args.String["<role>"])
+}
diff --git a/cmd/tuf/clean.go b/cmd/tuf/clean.go
new file mode 100644
index 0000000..303662b
--- /dev/null
+++ b/cmd/tuf/clean.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("clean", cmdClean, `
+usage: tuf clean
+
+Remove all staged metadata files.
+`)
+}
+
+func cmdClean(args *docopt.Args, repo *tuf.Repo) error {
+ err := repo.Clean()
+ if err == tuf.ErrNewRepository {
+ fmt.Fprintln(os.Stderr, "tuf: refusing to clean new repository")
+ return nil
+ }
+ return err
+}
diff --git a/cmd/tuf/commit.go b/cmd/tuf/commit.go
new file mode 100644
index 0000000..0bff3b2
--- /dev/null
+++ b/cmd/tuf/commit.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("commit", cmdCommit, `
+usage: tuf commit
+
+Commit staged files to the repository.
+`)
+}
+
+func cmdCommit(args *docopt.Args, repo *tuf.Repo) error {
+ return repo.Commit()
+}
diff --git a/cmd/tuf/gen_key.go b/cmd/tuf/gen_key.go
new file mode 100644
index 0000000..2ad77a5
--- /dev/null
+++ b/cmd/tuf/gen_key.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+func init() {
+ register("gen-key", cmdGenKey, `
+usage: tuf gen-key [--expires=<days>] [--scheme=<scheme>] <role>
+
+Generate a new signing key for the given role.
+
+The key will be serialized to JSON and written to the "keys" directory with
+filename pattern "ROLE-KEYID.json". The root metadata file will also be staged
+with the addition of the key's ID to the role's list of key IDs.
+
+Alternatively, passphrases can be set via environment variables in the
+form of TUF_{{ROLE}}_PASSPHRASE
+
+Options:
+ --expires=<days> Set the root metadata file to expire <days> days from now.
+ --scheme=<scheme> Set the key scheme to use [default: ed25519].
+`)
+}
+
+func cmdGenKey(args *docopt.Args, repo *tuf.Repo) error {
+ role := args.String["<role>"]
+ var keyids []string
+
+ keyScheme := data.KeySchemeEd25519
+ switch t := args.String["--scheme"]; t {
+ case string(data.KeySchemeEd25519),
+ string(data.KeySchemeECDSA_SHA2_P256),
+ string(data.KeySchemeRSASSA_PSS_SHA256):
+ keyScheme = data.KeyScheme(t)
+ default:
+ fmt.Fprint(os.Stderr, "Using default key scheme", keyScheme)
+ }
+
+ var err error
+ var expires time.Time
+ if arg := args.String["--expires"]; arg != "" {
+ expires, err = parseExpires(arg)
+ if err != nil {
+ return err
+ }
+ } else {
+ expires = data.DefaultExpires(role)
+ }
+ keyids, err = repo.GenKeyWithSchemeAndExpires(role, expires, keyScheme)
+ if err != nil {
+ return err
+ }
+ for _, id := range keyids {
+ fmt.Fprintf(os.Stdout, "Generated %s %s key with ID %s", role, keyScheme, id)
+ }
+ return nil
+}
diff --git a/cmd/tuf/get_threshold.go b/cmd/tuf/get_threshold.go
new file mode 100644
index 0000000..a0d78fd
--- /dev/null
+++ b/cmd/tuf/get_threshold.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("get-threshold", cmdGetThreshold, `
+usage: tuf get-threshold <role>
+
+Gets the threshold for a role.
+`)
+}
+
+func cmdGetThreshold(args *docopt.Args, repo *tuf.Repo) error {
+ role := args.String["<role>"]
+
+ threshold, err := repo.GetThreshold(role)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(os.Stdout, "The threshold for %s role is %d", role, threshold)
+ return nil
+}
diff --git a/cmd/tuf/init.go b/cmd/tuf/init.go
new file mode 100644
index 0000000..7c78549
--- /dev/null
+++ b/cmd/tuf/init.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("init", cmdInit, `
+usage: tuf init [--consistent-snapshot=false]
+
+Initialize a new repository.
+
+This is only required if the repository should not generate consistent
+snapshots (i.e. by passing "--consistent-snapshot=false"). If consistent
+snapshots should be generated, the repository will be implicitly
+initialized to do so when generating keys.
+ `)
+}
+
+func cmdInit(args *docopt.Args, repo *tuf.Repo) error {
+ return repo.Init(args.String["--consistent-snapshot"] != "false")
+}
diff --git a/cmd/tuf/main.go b/cmd/tuf/main.go
new file mode 100644
index 0000000..dc6a256
--- /dev/null
+++ b/cmd/tuf/main.go
@@ -0,0 +1,175 @@
+package main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+
+ docopt "github.com/flynn/go-docopt"
+ tuf "github.com/theupdateframework/go-tuf"
+ "github.com/theupdateframework/go-tuf/util"
+ "golang.org/x/term"
+)
+
+func main() {
+ log.SetFlags(0)
+
+ usage := `usage: tuf [-h|--help] [-d|--dir=<dir>] [--insecure-plaintext] <command> [<args>...]
+
+Options:
+ -h, --help
+ -d <dir> The path to the repository (defaults to the current working directory)
+ --insecure-plaintext Don't encrypt signing keys
+
+Commands:
+ help Show usage for a specific command
+ init Initialize a new repository
+ gen-key Generate a new signing key for a specific metadata file
+ revoke-key Revoke a signing key
+ add Add target file(s)
+ remove Remove a target file
+ snapshot Update the snapshot metadata file
+ timestamp Update the timestamp metadata file
+ payload Output a role's metadata file for signing
+ add-signatures Adds signatures generated offline
+ sign Sign a role's metadata file
+ sign-payload Sign a file from the "payload" command.
+ status Check if a role's metadata has expired
+ commit Commit staged files to the repository
+ regenerate Recreate the targets metadata file [Not supported yet]
+ set-threshold Sets the threshold for a role
+ get-threshold Outputs the threshold for a role
+ change-passphrase Changes the passphrase for given role keys file
+ root-keys Output a JSON serialized array of root keys to STDOUT
+ clean Remove all staged metadata files
+
+See "tuf help <command>" for more information on a specific command
+`
+
+ args, _ := docopt.Parse(usage, nil, true, "", true)
+ cmd := args.String["<command>"]
+ cmdArgs := args.All["<args>"].([]string)
+
+ if cmd == "help" {
+ if len(cmdArgs) == 0 { // `tuf help`
+ fmt.Fprint(os.Stdout, usage)
+ return
+ } else { // `tuf help <command>`
+ cmd = cmdArgs[0]
+ cmdArgs = []string{"--help"}
+ }
+ }
+
+ dir, ok := args.String["-d"]
+ if !ok {
+ dir = args.String["--dir"]
+ }
+ if dir == "" {
+ var err error
+ dir, err = os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ if err := runCommand(cmd, cmdArgs, dir, args.Bool["--insecure-plaintext"]); err != nil {
+ log.Fatalln("ERROR:", err)
+ }
+}
+
+type cmdFunc func(*docopt.Args, *tuf.Repo) error
+
+type command struct {
+ usage string
+ f cmdFunc
+}
+
+var commands = make(map[string]*command)
+
+func register(name string, f cmdFunc, usage string) {
+ commands[name] = &command{usage: usage, f: f}
+}
+
+func runCommand(name string, args []string, dir string, insecure bool) error {
+ argv := make([]string, 1, 1+len(args))
+ argv[0] = name
+ argv = append(argv, args...)
+
+ cmd, ok := commands[name]
+ if !ok {
+ return fmt.Errorf("%s is not a tuf command. See 'tuf help'", name)
+ }
+
+ parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", true)
+ if err != nil {
+ return err
+ }
+
+ var p util.PassphraseFunc
+ if !insecure {
+ p = getPassphrase
+ }
+ logger := log.New(os.Stdout, "", 0)
+ storeOpts := tuf.StoreOpts{Logger: logger, PassFunc: p}
+
+ repo, err := tuf.NewRepoWithOpts(tuf.FileSystemStoreWithOpts(dir, storeOpts),
+ tuf.WithLogger(logger))
+ if err != nil {
+ return err
+ }
+ return cmd.f(parsedArgs, repo)
+}
+
+func parseExpires(arg string) (time.Time, error) {
+ days, err := strconv.Atoi(arg)
+ if err != nil {
+ return time.Time{}, fmt.Errorf("failed to parse --expires arg: %s", err)
+ }
+ return time.Now().AddDate(0, 0, days).UTC(), nil
+}
+
+func getPassphrase(role string, confirm bool, change bool) ([]byte, error) {
+ // In case of change we need to prompt explicitly for a new passphrase
+ // and not read it from the environment variable, if present
+ if pass := os.Getenv(fmt.Sprintf("TUF_%s_PASSPHRASE", strings.ToUpper(role))); pass != "" && !change {
+ return []byte(pass), nil
+ }
+ // Alter role string if we are prompting for a passphrase change
+ if change {
+ // Check if environment variable for new passphrase exist
+ if new_pass := os.Getenv(fmt.Sprintf("TUF_NEW_%s_PASSPHRASE", strings.ToUpper(role))); new_pass != "" {
+ // If so, just read the new passphrase from it and return
+ return []byte(new_pass), nil
+ }
+ // No environment variable set, so proceed prompting for new passphrase
+ role = fmt.Sprintf("new %s", role)
+ }
+ fmt.Fprintf(os.Stderr, "Enter %s keys passphrase: ", role)
+ passphrase, err := term.ReadPassword(int(syscall.Stdin))
+ fmt.Fprintln(os.Stderr)
+ if err != nil {
+ return nil, err
+ }
+
+ if !confirm {
+ return passphrase, nil
+ }
+
+ fmt.Fprintf(os.Stderr, "Repeat %s keys passphrase: ", role)
+ confirmation, err := term.ReadPassword(int(syscall.Stdin))
+ fmt.Fprintln(os.Stderr)
+ if err != nil {
+ return nil, err
+ }
+
+ if !bytes.Equal(passphrase, confirmation) {
+ return nil, errors.New("the entered passphrases do not match")
+ }
+ return passphrase, nil
+}
diff --git a/cmd/tuf/payload.go b/cmd/tuf/payload.go
new file mode 100644
index 0000000..3ae2c89
--- /dev/null
+++ b/cmd/tuf/payload.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("payload", cmdPayload, `
+usage: tuf payload <metadata>
+
+Outputs the metadata file for a role in a ready-to-sign (canonicalized) format.
+`)
+}
+
+func cmdPayload(args *docopt.Args, repo *tuf.Repo) error {
+ p, err := repo.Payload(args.String["<metadata>"])
+ if err != nil {
+ return err
+ }
+ fmt.Fprint(os.Stdout, string(p))
+ return nil
+}
diff --git a/cmd/tuf/regenerate.go b/cmd/tuf/regenerate.go
new file mode 100644
index 0000000..bfcbbad
--- /dev/null
+++ b/cmd/tuf/regenerate.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "log"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("regenerate", cmdRegenerate, `
+usage: tuf regenerate [--consistent-snapshot=false]
+
+Recreate the targets metadata file. Important: Not supported yet
+
+Alternatively, passphrases can be set via environment variables in the
+form of TUF_{{ROLE}}_PASSPHRASE
+`)
+}
+
+func cmdRegenerate(args *docopt.Args, repo *tuf.Repo) error {
+ // TODO: implement this
+ log.Println("Not supported yet")
+ return nil
+}
diff --git a/cmd/tuf/remove.go b/cmd/tuf/remove.go
new file mode 100644
index 0000000..e1e0b77
--- /dev/null
+++ b/cmd/tuf/remove.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+ "errors"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("remove", cmdRemove, `
+usage: tuf remove [--expires=<days>] [--all] [<path>...]
+
+Remove target file(s).
+
+Alternatively, passphrases can be set via environment variables in the
+form of TUF_{{ROLE}}_PASSPHRASE
+
+Options:
+ --all Remove all target files.
+ --expires=<days> Set the targets metadata file to expire <days> days from now.
+`)
+}
+
+func cmdRemove(args *docopt.Args, repo *tuf.Repo) error {
+ paths := args.All["<path>"].([]string)
+ if len(paths) == 0 && !args.Bool["--all"] {
+ return errors.New("either specify some paths or set the --all flag to remove all targets")
+ }
+ if arg := args.String["--expires"]; arg != "" {
+ expires, err := parseExpires(arg)
+ if err != nil {
+ return err
+ }
+ return repo.RemoveTargetsWithExpires(paths, expires)
+ }
+ return repo.RemoveTargets(paths)
+}
diff --git a/cmd/tuf/revoke_key.go b/cmd/tuf/revoke_key.go
new file mode 100644
index 0000000..8aff2e0
--- /dev/null
+++ b/cmd/tuf/revoke_key.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("revoke-key", cmdRevokeKey, `
+usage: tuf revoke-key [--expires=<days>] <role> <id>
+
+Revoke a signing key
+
+The key will be removed from the root metadata file, but the key will remain in the
+"keys" directory if present.
+
+Options:
+ --expires=<days> Set the root metadata file to expire <days> days from now.
+`)
+}
+
+func cmdRevokeKey(args *docopt.Args, repo *tuf.Repo) error {
+ if arg := args.String["--expires"]; arg != "" {
+ expires, err := parseExpires(arg)
+ if err != nil {
+ return err
+ }
+ return repo.RevokeKeyWithExpires(args.String["<role>"], args.String["<id>"], expires)
+ }
+ return repo.RevokeKey(args.String["<role>"], args.String["<id>"])
+}
diff --git a/cmd/tuf/root_keys.go b/cmd/tuf/root_keys.go
new file mode 100644
index 0000000..e339764
--- /dev/null
+++ b/cmd/tuf/root_keys.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("root-keys", cmdRootKeys, `
+usage: tuf root-keys
+
+Outputs a JSON serialized array of root keys to STDOUT.
+
+The resulting JSON should be distributed to clients for performing initial updates.
+`)
+}
+
+func cmdRootKeys(args *docopt.Args, repo *tuf.Repo) error {
+ keys, err := repo.RootKeys()
+ if err != nil {
+ return err
+ }
+ data, err := json.Marshal(keys)
+ if err == nil {
+ fmt.Fprintf(os.Stderr, "The resulting JSON should be distributed to clients for performing initial updates:\n\n")
+ fmt.Fprintln(os.Stdout, string(data))
+ }
+ return err
+}
diff --git a/cmd/tuf/set_threshold.go b/cmd/tuf/set_threshold.go
new file mode 100644
index 0000000..29149ff
--- /dev/null
+++ b/cmd/tuf/set_threshold.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("set-threshold", cmdSetThreshold, `
+usage: tuf set-threshold <role> <threshold>
+
+Set the threshold for a role.
+`)
+}
+
+func cmdSetThreshold(args *docopt.Args, repo *tuf.Repo) error {
+ role := args.String["<role>"]
+ thresholdStr := args.String["<threshold>"]
+ threshold, err := strconv.Atoi(thresholdStr)
+ if err != nil {
+ return err
+ }
+
+ if err := repo.SetThreshold(role, threshold); err != nil {
+ return err
+ }
+
+ fmt.Fprintf(os.Stdout, "The threshold for %s role is now %d", role, threshold)
+ return nil
+}
diff --git a/cmd/tuf/sign.go b/cmd/tuf/sign.go
new file mode 100644
index 0000000..ee8917b
--- /dev/null
+++ b/cmd/tuf/sign.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("sign", cmdSign, `
+usage: tuf sign <metadata>
+
+Sign a role's metadata file.
+
+Signs the given role's staged metadata file with all keys present in the 'keys'
+directory for that role.
+`)
+}
+
+func cmdSign(args *docopt.Args, repo *tuf.Repo) error {
+ return repo.Sign(args.String["<metadata>"])
+}
diff --git a/cmd/tuf/sign_payload.go b/cmd/tuf/sign_payload.go
new file mode 100644
index 0000000..1905fd2
--- /dev/null
+++ b/cmd/tuf/sign_payload.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/flynn/go-docopt"
+ tuf "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("sign-payload", cmdSignPayload, `
+usage: tuf sign-payload --role=<role> <path>
+
+Sign a file (outside of the TUF repo) using keys for the given role (from the TUF repo).
+
+Typically, path will be the output of "tuf payload".
+`)
+}
+
+func cmdSignPayload(args *docopt.Args, repo *tuf.Repo) error {
+ payload, err := os.ReadFile(args.String["<path>"])
+ if err != nil {
+ return err
+ }
+
+ signatures, err := repo.SignRaw(args.String["--role"], payload)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintln(os.Stderr, "tuf: signed")
+
+ bytes, err := json.Marshal(signatures)
+ if err != nil {
+ return err
+ }
+ fmt.Fprint(os.Stdout, string(bytes))
+
+ return nil
+}
diff --git a/cmd/tuf/snapshot.go b/cmd/tuf/snapshot.go
new file mode 100644
index 0000000..dfffb69
--- /dev/null
+++ b/cmd/tuf/snapshot.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("snapshot", cmdSnapshot, `
+usage: tuf snapshot [--expires=<days>]
+
+Update the snapshot metadata file.
+
+Alternatively, passphrases can be set via environment variables in the
+form of TUF_{{ROLE}}_PASSPHRASE
+
+Options:
+ --expires=<days> Set the snapshot metadata file to expire <days> days from now.
+`)
+}
+
+func cmdSnapshot(args *docopt.Args, repo *tuf.Repo) error {
+ if arg := args.String["--expires"]; arg != "" {
+ expires, err := parseExpires(arg)
+ if err != nil {
+ return err
+ }
+ return repo.SnapshotWithExpires(expires)
+ }
+ return repo.Snapshot()
+}
diff --git a/cmd/tuf/status.go b/cmd/tuf/status.go
new file mode 100644
index 0000000..97568e7
--- /dev/null
+++ b/cmd/tuf/status.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("status", cmdStatus, `
+usage: tuf status --valid-at=<date> <role>
+
+Check if the role's metadata will be expired on the given date.
+
+The command's exit status will be 1 if the role has expired, 0 otherwise.
+
+Example:
+ # See if timestamp metadata is expiring in the next hour:
+ tuf status --valid-at "$(date -d '+1 hour')" timestamp || echo "Time to refresh"
+
+Options:
+ --valid-at=<date> Must be in one of the formats:
+ * RFC3339 - 2006-01-02T15:04:05Z07:00
+ * RFC822 - 02 Jan 06 15:04 MST
+ * UnixDate - Mon Jan _2 15:04:05 MST 2006
+`)
+}
+
+func cmdStatus(args *docopt.Args, repo *tuf.Repo) error {
+ role := args.String["<role>"]
+ validAtStr := args.String["--valid-at"]
+
+ formats := []string{
+ time.RFC3339,
+ time.RFC822,
+ time.UnixDate,
+ }
+ for _, fmt := range formats {
+ validAt, err := time.Parse(fmt, validAtStr)
+ if err == nil {
+ return repo.CheckRoleUnexpired(role, validAt)
+ }
+ }
+ return fmt.Errorf("failed to parse --valid-at arg")
+}
diff --git a/cmd/tuf/timestamp.go b/cmd/tuf/timestamp.go
new file mode 100644
index 0000000..646f174
--- /dev/null
+++ b/cmd/tuf/timestamp.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "github.com/flynn/go-docopt"
+ "github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+ register("timestamp", cmdTimestamp, `
+usage: tuf timestamp [--expires=<days>]
+
+Update the timestamp metadata file.
+
+Alternatively, passphrases can be set via environment variables in the
+form of TUF_{{ROLE}}_PASSPHRASE
+
+Options:
+ --expires=<days> Set the timestamp metadata file to expire <days> days from now.
+`)
+}
+
+func cmdTimestamp(args *docopt.Args, repo *tuf.Repo) error {
+ if arg := args.String["--expires"]; arg != "" {
+ expires, err := parseExpires(arg)
+ if err != nil {
+ return err
+ }
+ return repo.TimestampWithExpires(expires)
+ }
+ return repo.Timestamp()
+}
diff --git a/data/hex_bytes.go b/data/hex_bytes.go
new file mode 100644
index 0000000..ec20041
--- /dev/null
+++ b/data/hex_bytes.go
@@ -0,0 +1,42 @@
+package data
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "errors"
+)
+
+type HexBytes []byte
+
+func (b *HexBytes) UnmarshalJSON(data []byte) error {
+ if len(data) < 2 || len(data)%2 != 0 || data[0] != '"' || data[len(data)-1] != '"' {
+ return errors.New("tuf: invalid JSON hex bytes")
+ }
+ res := make([]byte, hex.DecodedLen(len(data)-2))
+ _, err := hex.Decode(res, data[1:len(data)-1])
+ if err != nil {
+ return err
+ }
+ *b = res
+ return nil
+}
+
+func (b HexBytes) MarshalJSON() ([]byte, error) {
+ res := make([]byte, hex.EncodedLen(len(b))+2)
+ res[0] = '"'
+ res[len(res)-1] = '"'
+ hex.Encode(res[1:], b)
+ return res, nil
+}
+
+func (b HexBytes) String() string {
+ return hex.EncodeToString(b)
+}
+
+// 4.5. File formats: targets.json and delegated target roles:
+// ...each target path, when hashed with the SHA-256 hash function to produce
+// a 64-byte hexadecimal digest (HEX_DIGEST)...
+func PathHexDigest(s string) string {
+ b := sha256.Sum256([]byte(s))
+ return hex.EncodeToString(b[:])
+}
diff --git a/data/hex_bytes_test.go b/data/hex_bytes_test.go
new file mode 100644
index 0000000..8c11623
--- /dev/null
+++ b/data/hex_bytes_test.go
@@ -0,0 +1,44 @@
+package data
+
+import (
+ "encoding/json"
+ "testing"
+
+ . "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type HexBytesSuite struct{}
+
+var _ = Suite(&HexBytesSuite{})
+
+func (HexBytesSuite) TestUnmarshalJSON(c *C) {
+ var data HexBytes
+ err := json.Unmarshal([]byte(`"666f6f"`), &data)
+ c.Assert(err, IsNil)
+ c.Assert(string(data), Equals, "foo")
+}
+
+func (HexBytesSuite) TestUnmarshalJSONError(c *C) {
+ var data HexBytes
+
+ // uneven length
+ err := json.Unmarshal([]byte(`"a"`), &data)
+ c.Assert(err, Not(IsNil))
+
+ // invalid hex
+ err = json.Unmarshal([]byte(`"zz"`), &data)
+ c.Assert(err, Not(IsNil))
+
+ // wrong type
+ err = json.Unmarshal([]byte("6"), &data)
+ c.Assert(err, Not(IsNil))
+}
+
+func (HexBytesSuite) TestMarshalJSON(c *C) {
+ data, err := json.Marshal(HexBytes("foo"))
+ c.Assert(err, IsNil)
+ c.Assert(data, DeepEquals, []byte(`"666f6f"`))
+}
diff --git a/data/types.go b/data/types.go
new file mode 100644
index 0000000..eb00489
--- /dev/null
+++ b/data/types.go
@@ -0,0 +1,348 @@
+package data
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "path"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+)
+
+type KeyType string
+
+type KeyScheme string
+
+type HashAlgorithm string
+
+const (
+ KeyIDLength = sha256.Size * 2
+
+ KeyTypeEd25519 KeyType = "ed25519"
+ // From version 1.0.32, the reference implementation defines 'ecdsa',
+ // not 'ecdsa-sha2-nistp256' for NIST P-256 curves.
+ KeyTypeECDSA_SHA2_P256 KeyType = "ecdsa"
+ KeyTypeECDSA_SHA2_P256_OLD_FMT KeyType = "ecdsa-sha2-nistp256"
+ KeyTypeRSASSA_PSS_SHA256 KeyType = "rsa"
+
+ KeySchemeEd25519 KeyScheme = "ed25519"
+ KeySchemeECDSA_SHA2_P256 KeyScheme = "ecdsa-sha2-nistp256"
+ KeySchemeRSASSA_PSS_SHA256 KeyScheme = "rsassa-pss-sha256"
+
+ HashAlgorithmSHA256 HashAlgorithm = "sha256"
+ HashAlgorithmSHA512 HashAlgorithm = "sha512"
+)
+
+var (
+ HashAlgorithms = []HashAlgorithm{HashAlgorithmSHA256, HashAlgorithmSHA512}
+ ErrPathsAndPathHashesSet = errors.New("tuf: failed validation of delegated target: paths and path_hash_prefixes are both set")
+)
+
+type Signed struct {
+ Signed json.RawMessage `json:"signed"`
+ Signatures []Signature `json:"signatures"`
+}
+
+type Signature struct {
+ KeyID string `json:"keyid"`
+ Signature HexBytes `json:"sig"`
+}
+
+type PublicKey struct {
+ Type KeyType `json:"keytype"`
+ Scheme KeyScheme `json:"scheme"`
+ Algorithms []HashAlgorithm `json:"keyid_hash_algorithms,omitempty"`
+ Value json.RawMessage `json:"keyval"`
+
+ ids []string
+ idOnce sync.Once
+}
+
+type PrivateKey struct {
+ Type KeyType `json:"keytype"`
+ Scheme KeyScheme `json:"scheme,omitempty"`
+ Algorithms []HashAlgorithm `json:"keyid_hash_algorithms,omitempty"`
+ Value json.RawMessage `json:"keyval"`
+}
+
+func (k *PublicKey) IDs() []string {
+ k.idOnce.Do(func() {
+ data, err := cjson.EncodeCanonical(k)
+ if err != nil {
+ panic(fmt.Errorf("tuf: error creating key ID: %w", err))
+ }
+ digest := sha256.Sum256(data)
+ k.ids = []string{hex.EncodeToString(digest[:])}
+ })
+ return k.ids
+}
+
+func (k *PublicKey) ContainsID(id string) bool {
+ for _, keyid := range k.IDs() {
+ if id == keyid {
+ return true
+ }
+ }
+ return false
+}
+
+func DefaultExpires(role string) time.Time {
+ var t time.Time
+ switch role {
+ case "root":
+ t = time.Now().AddDate(1, 0, 0)
+ case "snapshot":
+ t = time.Now().AddDate(0, 0, 7)
+ case "timestamp":
+ t = time.Now().AddDate(0, 0, 1)
+ default:
+ // targets and delegated targets
+ t = time.Now().AddDate(0, 3, 0)
+ }
+ return t.UTC().Round(time.Second)
+}
+
+type Root struct {
+ Type string `json:"_type"`
+ SpecVersion string `json:"spec_version"`
+ Version int64 `json:"version"`
+ Expires time.Time `json:"expires"`
+ Keys map[string]*PublicKey `json:"keys"`
+ Roles map[string]*Role `json:"roles"`
+ Custom *json.RawMessage `json:"custom,omitempty"`
+
+ ConsistentSnapshot bool `json:"consistent_snapshot"`
+}
+
+func NewRoot() *Root {
+ return &Root{
+ Type: "root",
+ SpecVersion: "1.0",
+ Expires: DefaultExpires("root"),
+ Keys: make(map[string]*PublicKey),
+ Roles: make(map[string]*Role),
+ ConsistentSnapshot: true,
+ }
+}
+
+func (r *Root) AddKey(key *PublicKey) bool {
+ changed := false
+ for _, id := range key.IDs() {
+ if _, ok := r.Keys[id]; !ok {
+ changed = true
+ r.Keys[id] = key
+ }
+ }
+ return changed
+}
+
+type Role struct {
+ KeyIDs []string `json:"keyids"`
+ Threshold int `json:"threshold"`
+}
+
+func (r *Role) AddKeyIDs(ids []string) bool {
+ roleIDs := make(map[string]struct{})
+ for _, id := range r.KeyIDs {
+ roleIDs[id] = struct{}{}
+ }
+ changed := false
+ for _, id := range ids {
+ if _, ok := roleIDs[id]; !ok {
+ changed = true
+ r.KeyIDs = append(r.KeyIDs, id)
+ }
+ }
+ return changed
+}
+
+type Files map[string]TargetFileMeta
+
+type Hashes map[string]HexBytes
+
+func (f Hashes) HashAlgorithms() []string {
+ funcs := make([]string, 0, len(f))
+ for name := range f {
+ funcs = append(funcs, name)
+ }
+ return funcs
+}
+
+type metapathFileMeta struct {
+ Length int64 `json:"length,omitempty"`
+ Hashes Hashes `json:"hashes,omitempty"`
+ Version int64 `json:"version"`
+ Custom *json.RawMessage `json:"custom,omitempty"`
+}
+
+// SnapshotFileMeta is the meta field of a snapshot
+// Note: Contains a `custom` field
+type SnapshotFileMeta metapathFileMeta
+
+type SnapshotFiles map[string]SnapshotFileMeta
+
+type Snapshot struct {
+ Type string `json:"_type"`
+ SpecVersion string `json:"spec_version"`
+ Version int64 `json:"version"`
+ Expires time.Time `json:"expires"`
+ Meta SnapshotFiles `json:"meta"`
+ Custom *json.RawMessage `json:"custom,omitempty"`
+}
+
+func NewSnapshot() *Snapshot {
+ return &Snapshot{
+ Type: "snapshot",
+ SpecVersion: "1.0",
+ Expires: DefaultExpires("snapshot"),
+ Meta: make(SnapshotFiles),
+ }
+}
+
+type FileMeta struct {
+ Length int64 `json:"length"`
+ Hashes Hashes `json:"hashes"`
+}
+
+type TargetFiles map[string]TargetFileMeta
+
+type TargetFileMeta struct {
+ FileMeta
+ Custom *json.RawMessage `json:"custom,omitempty"`
+}
+
+func (f TargetFileMeta) HashAlgorithms() []string {
+ return f.FileMeta.Hashes.HashAlgorithms()
+}
+
+type Targets struct {
+ Type string `json:"_type"`
+ SpecVersion string `json:"spec_version"`
+ Version int64 `json:"version"`
+ Expires time.Time `json:"expires"`
+ Targets TargetFiles `json:"targets"`
+ Delegations *Delegations `json:"delegations,omitempty"`
+ Custom *json.RawMessage `json:"custom,omitempty"`
+}
+
+// Delegations represents the edges from a parent Targets role to one or more
+// delegated target roles. See spec v1.0.19 section 4.5.
+type Delegations struct {
+ Keys map[string]*PublicKey `json:"keys"`
+ Roles []DelegatedRole `json:"roles"`
+}
+
+// DelegatedRole describes a delegated role, including what paths it is
+// reponsible for. See spec v1.0.19 section 4.5.
+type DelegatedRole struct {
+ Name string `json:"name"`
+ KeyIDs []string `json:"keyids"`
+ Threshold int `json:"threshold"`
+ Terminating bool `json:"terminating"`
+ PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"`
+ Paths []string `json:"paths"`
+}
+
+// MatchesPath evaluates whether the path patterns or path hash prefixes match
+// a given file. This determines whether a delegated role is responsible for
+// signing and verifying the file.
+func (d *DelegatedRole) MatchesPath(file string) (bool, error) {
+ if err := d.validatePaths(); err != nil {
+ return false, err
+ }
+
+ for _, pattern := range d.Paths {
+ if matched, _ := path.Match(pattern, file); matched {
+ return true, nil
+ }
+ }
+
+ pathHash := PathHexDigest(file)
+ for _, hashPrefix := range d.PathHashPrefixes {
+ if strings.HasPrefix(pathHash, hashPrefix) {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+// validatePaths enforces the spec
+// https://theupdateframework.github.io/specification/v1.0.19/index.html#file-formats-targets
+// 'role MUST specify only one of the "path_hash_prefixes" or "paths"'
+// Marshalling and unmarshalling JSON will fail and return
+// ErrPathsAndPathHashesSet if both fields are set and not empty.
+func (d *DelegatedRole) validatePaths() error {
+ if len(d.PathHashPrefixes) > 0 && len(d.Paths) > 0 {
+ return ErrPathsAndPathHashesSet
+ }
+
+ return nil
+}
+
+// MarshalJSON is called when writing the struct to JSON. We validate prior to
+// marshalling to ensure that an invalid delegated role can not be serialized
+// to JSON.
+func (d *DelegatedRole) MarshalJSON() ([]byte, error) {
+ type delegatedRoleAlias DelegatedRole
+
+ if err := d.validatePaths(); err != nil {
+ return nil, err
+ }
+
+ return json.Marshal((*delegatedRoleAlias)(d))
+}
+
+// UnmarshalJSON is called when reading the struct from JSON. We validate once
+// unmarshalled to ensure that an error is thrown if an invalid delegated role
+// is read.
+func (d *DelegatedRole) UnmarshalJSON(b []byte) error {
+ type delegatedRoleAlias DelegatedRole
+
+ // Prepare decoder
+ dec := json.NewDecoder(bytes.NewReader(b))
+
+ // Unmarshal delegated role
+ if err := dec.Decode((*delegatedRoleAlias)(d)); err != nil {
+ return err
+ }
+
+ return d.validatePaths()
+}
+
+func NewTargets() *Targets {
+ return &Targets{
+ Type: "targets",
+ SpecVersion: "1.0",
+ Expires: DefaultExpires("targets"),
+ Targets: make(TargetFiles),
+ }
+}
+
+type TimestampFileMeta metapathFileMeta
+
+type TimestampFiles map[string]TimestampFileMeta
+
+type Timestamp struct {
+ Type string `json:"_type"`
+ SpecVersion string `json:"spec_version"`
+ Version int64 `json:"version"`
+ Expires time.Time `json:"expires"`
+ Meta TimestampFiles `json:"meta"`
+ Custom *json.RawMessage `json:"custom,omitempty"`
+}
+
+func NewTimestamp() *Timestamp {
+ return &Timestamp{
+ Type: "timestamp",
+ SpecVersion: "1.0",
+ Expires: DefaultExpires("timestamp"),
+ Meta: make(TimestampFiles),
+ }
+}
diff --git a/data/types_test.go b/data/types_test.go
new file mode 100644
index 0000000..b9523fa
--- /dev/null
+++ b/data/types_test.go
@@ -0,0 +1,287 @@
+package data
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+ "github.com/stretchr/testify/assert"
+ . "gopkg.in/check.v1"
+)
+
+const (
+ // This public key is from the TUF specs:
+ //
+ // https://github.com/theupdateframework/specification
+ //
+ public = `"72378e5bc588793e58f81c8533da64a2e8f1565c1fcc7f253496394ffc52542c"`
+ keyid10 = "1bf1c6e3cdd3d3a8420b19199e27511999850f4b376c4547b2f32fba7e80fca3"
+ keyid10algos = "506a349b85945d0d99c7289c3f0f1f6c550218089d1d38a3f64824db31e827ac"
+)
+
+type TypesSuite struct{}
+
+var _ = Suite(&TypesSuite{})
+
+type ed25519Public struct {
+ PublicKey HexBytes `json:"public"`
+}
+
+func (TypesSuite) TestKeyIDs(c *C) {
+ var hexbytes HexBytes
+ err := json.Unmarshal([]byte(public), &hexbytes)
+ c.Assert(err, IsNil)
+ keyValBytes, err := json.Marshal(ed25519Public{PublicKey: hexbytes})
+ c.Assert(err, IsNil)
+
+ key := PublicKey{
+ Type: KeyTypeEd25519,
+ Scheme: KeySchemeEd25519,
+ Value: keyValBytes,
+ }
+ c.Assert(key.IDs(), DeepEquals, []string{keyid10})
+
+ key = PublicKey{
+ Type: KeyTypeEd25519,
+ Scheme: KeySchemeEd25519,
+ Algorithms: HashAlgorithms,
+ Value: keyValBytes,
+ }
+ c.Assert(key.IDs(), DeepEquals, []string{keyid10algos})
+}
+
+func (TypesSuite) TestRootAddKey(c *C) {
+ var hexbytes HexBytes
+ err := json.Unmarshal([]byte(public), &hexbytes)
+ c.Assert(err, IsNil)
+ keyValBytes, err := json.Marshal(ed25519Public{PublicKey: hexbytes})
+ c.Assert(err, IsNil)
+
+ key := &PublicKey{
+ Type: KeyTypeEd25519,
+ Scheme: KeySchemeEd25519,
+ Value: keyValBytes,
+ }
+
+ root := NewRoot()
+
+ c.Assert(root.AddKey(key), Equals, true)
+ c.Assert(root.AddKey(key), Equals, false)
+}
+
+func (TypesSuite) TestRoleAddKeyIDs(c *C) {
+ var hexbytes HexBytes
+ err := json.Unmarshal([]byte(public), &hexbytes)
+ c.Assert(err, IsNil)
+ keyValBytes, err := json.Marshal(ed25519Public{PublicKey: hexbytes})
+ c.Assert(err, IsNil)
+
+ key := &PublicKey{
+ Type: KeyTypeEd25519,
+ Scheme: KeySchemeEd25519,
+ Value: keyValBytes,
+ }
+
+ role := &Role{}
+ c.Assert(role.KeyIDs, HasLen, 0)
+
+ c.Assert(role.AddKeyIDs(key.IDs()), Equals, true)
+ c.Assert(role.KeyIDs, DeepEquals, []string{keyid10})
+
+ // Adding the key again doesn't modify the array.
+ c.Assert(role.AddKeyIDs(key.IDs()), Equals, false)
+ c.Assert(role.KeyIDs, DeepEquals, []string{keyid10})
+
+ // Add another key.
+ key = &PublicKey{
+ Type: KeyTypeEd25519,
+ Scheme: KeySchemeEd25519,
+ Algorithms: HashAlgorithms,
+ Value: keyValBytes,
+ }
+
+ // Adding the key again doesn't modify the array.
+ c.Assert(role.AddKeyIDs(key.IDs()), Equals, true)
+ c.Assert(role.KeyIDs, DeepEquals, []string{keyid10, keyid10algos})
+}
+
+func TestDelegatedRolePathMatch(t *testing.T) {
+ var tts = []struct {
+ testName string
+ pathPatterns []string
+ pathHashPrefixes []string
+ file string
+ shouldMatch bool
+ }{
+ {
+ testName: "no path",
+ file: "licence.txt",
+ },
+ {
+ testName: "match path *",
+ pathPatterns: []string{"null", "targets/*.tgz"},
+ file: "targets/foo.tgz",
+ shouldMatch: true,
+ },
+ {
+ testName: "does not match path *",
+ pathPatterns: []string{"null", "targets/*.tgz"},
+ file: "targets/foo.txt",
+ shouldMatch: false,
+ },
+ {
+ testName: "match path ?",
+ pathPatterns: []string{"foo-version-?.tgz"},
+ file: "foo-version-a.tgz",
+ shouldMatch: true,
+ },
+ {
+ testName: "does not match ?",
+ pathPatterns: []string{"foo-version-?.tgz"},
+ file: "foo-version-alpha.tgz",
+ shouldMatch: false,
+ },
+ // picked from https://github.com/theupdateframework/tuf/blob/30ba6e9f9ab25e0370e29ce574dada2d8809afa0/tests/test_updater.py#L1726-L1734
+ {
+ testName: "match hash prefix",
+ pathHashPrefixes: []string{"badd", "8baf"},
+ file: "/file3.txt",
+ shouldMatch: true,
+ },
+ {
+ testName: "does not match hash prefix",
+ pathHashPrefixes: []string{"badd"},
+ file: "/file3.txt",
+ shouldMatch: false,
+ },
+ {
+ testName: "hash prefix first char",
+ pathHashPrefixes: []string{"2"},
+ file: "/a/b/c/file_d.txt",
+ shouldMatch: true,
+ },
+ {
+ testName: "full hash prefix",
+ pathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633472a49096ed87f8f764bd597831eac371f40ac39"},
+ file: "/e/f/g.txt",
+ shouldMatch: true,
+ },
+ }
+ for _, tt := range tts {
+ t.Run(tt.testName, func(t *testing.T) {
+ d := DelegatedRole{
+ Paths: tt.pathPatterns,
+ PathHashPrefixes: tt.pathHashPrefixes,
+ }
+ assert.NoError(t, d.validatePaths())
+
+ matchesPath, err := d.MatchesPath(tt.file)
+ assert.NoError(t, err)
+ assert.Equal(t, tt.shouldMatch, matchesPath)
+ })
+
+ }
+}
+
+func TestDelegatedRoleJSON(t *testing.T) {
+ var tts = []struct {
+ testName string
+ d *DelegatedRole
+ rawCJSON string
+ }{{
+ testName: "all fields with hashes",
+ d: &DelegatedRole{
+ Name: "n1",
+ KeyIDs: []string{"k1"},
+ Threshold: 5,
+ Terminating: true,
+ PathHashPrefixes: []string{"8f"},
+ },
+ rawCJSON: `{"keyids":["k1"],"name":"n1","path_hash_prefixes":["8f"],"paths":null,"terminating":true,"threshold":5}`,
+ },
+ {
+ testName: "paths only",
+ d: &DelegatedRole{
+ Name: "n2",
+ KeyIDs: []string{"k1", "k3"},
+ Threshold: 12,
+ Paths: []string{"*.txt"},
+ },
+ rawCJSON: `{"keyids":["k1","k3"],"name":"n2","paths":["*.txt"],"terminating":false,"threshold":12}`,
+ },
+ {
+ testName: "default",
+ d: &DelegatedRole{},
+ rawCJSON: `{"keyids":null,"name":"","paths":null,"terminating":false,"threshold":0}`,
+ },
+ }
+
+ for _, tt := range tts {
+ t.Run(tt.testName, func(t *testing.T) {
+ b, err := cjson.EncodeCanonical(tt.d)
+ assert.NoError(t, err)
+ assert.Equal(t, tt.rawCJSON, string(b))
+
+ newD := &DelegatedRole{}
+ err = json.Unmarshal(b, newD)
+ assert.NoError(t, err)
+ assert.Equal(t, tt.d, newD)
+ })
+ }
+}
+
+func TestDelegatedRoleUnmarshalErr(t *testing.T) {
+ targetsWithBothMatchers := []byte(`{"keyids":null,"name":"","paths":["*.txt"],"path_hash_prefixes":["8f"],"terminating":false,"threshold":0}`)
+ var d DelegatedRole
+ assert.Equal(t, ErrPathsAndPathHashesSet, json.Unmarshal(targetsWithBothMatchers, &d))
+
+ // test for type errors
+ err := json.Unmarshal([]byte(`{"keyids":"a"}`), &d)
+ assert.Equal(t, "keyids", err.(*json.UnmarshalTypeError).Field)
+}
+
+func TestCustomField(t *testing.T) {
+ testCustomJSON := json.RawMessage([]byte(`{"test":true}`))
+
+ root := Root{
+ Type: "root",
+ SpecVersion: "1.0",
+ Keys: make(map[string]*PublicKey),
+ Roles: make(map[string]*Role),
+ ConsistentSnapshot: true,
+ Custom: &testCustomJSON,
+ }
+ rootJSON, err := json.Marshal(&root)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("{\"_type\":\"root\",\"spec_version\":\"1.0\",\"version\":0,\"expires\":\"0001-01-01T00:00:00Z\",\"keys\":{},\"roles\":{},\"custom\":{\"test\":true},\"consistent_snapshot\":true}"), rootJSON)
+
+ targets := Targets{
+ Type: "targets",
+ SpecVersion: "1.0",
+ Targets: make(TargetFiles),
+ Custom: &testCustomJSON,
+ }
+ targetsJSON, err := json.Marshal(&targets)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("{\"_type\":\"targets\",\"spec_version\":\"1.0\",\"version\":0,\"expires\":\"0001-01-01T00:00:00Z\",\"targets\":{},\"custom\":{\"test\":true}}"), targetsJSON)
+
+ snapshot := Snapshot{
+ Type: "snapshot",
+ SpecVersion: "1.0",
+ Meta: make(SnapshotFiles),
+ Custom: &testCustomJSON,
+ }
+ snapshotJSON, err := json.Marshal(&snapshot)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("{\"_type\":\"snapshot\",\"spec_version\":\"1.0\",\"version\":0,\"expires\":\"0001-01-01T00:00:00Z\",\"meta\":{},\"custom\":{\"test\":true}}"), snapshotJSON)
+
+ timestamp := Timestamp{
+ Type: "timestamp",
+ SpecVersion: "1.0",
+ Meta: make(TimestampFiles),
+ Custom: &testCustomJSON,
+ }
+ timestampJSON, err := json.Marshal(&timestamp)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("{\"_type\":\"timestamp\",\"spec_version\":\"1.0\",\"version\":0,\"expires\":\"0001-01-01T00:00:00Z\",\"meta\":{},\"custom\":{\"test\":true}}"), timestampJSON)
+}
diff --git a/docs/ALUMNI b/docs/ALUMNI
new file mode 100644
index 0000000..294df41
--- /dev/null
+++ b/docs/ALUMNI
@@ -0,0 +1,4 @@
+Christian Rebischke <chris@shibumi.dev> (github: shibumi)
+Erick Tryzelaar <etryzelaar@google.com> (github: erickt)
+Jonathan Rudenberg <jonathan@flynn.io> (github: titanous)
+Lewis Marshall <lewis@flynn.io> (github: lmars) \ No newline at end of file
diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..e9fdc17
--- /dev/null
+++ b/docs/CODE_OF_CONDUCT.md
@@ -0,0 +1,4 @@
+## The Update Framework Community Code of Conduct
+
+The Update Framework follows the [CNCF Code of
+Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md)
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
new file mode 100644
index 0000000..4336fec
--- /dev/null
+++ b/docs/CONTRIBUTING.md
@@ -0,0 +1,62 @@
+# Contributing Guide
+
+We welcome and encourage community contributions to go-tuf.
+
+Please familiarize yourself with these Contribution Guidelines before contributing.
+
+There are many ways to help go-tuf besides contributing code:
+
+- Fix bugs or file issues.
+- Provide feedback on the CLI experience or suggest feature enhancements.
+- Improve documentation.
+
+Please follow the [code of conduct](CODE_OF_CONDUCT.md) when contributing to this project.
+
+## Contributing Code
+
+Unless you are fixing a known bug, we strongly recommend discussing it with the community via a GitHub issue or Slack (see [Communication](#communication) below for details) before getting started to ensure that your work is consistent with TUF's specification.
+
+All contributions are made via pull request. All patches from all contributors get reviewed. See the [Pull Request procedure](#pull-request-procedure).
+
+
+## Pull Request Procedure
+
+To make a pull request, you will need a GitHub account. See GitHub's documentation on [forking](https://help.github.com/articles/fork-a-repo) and [pull requests](https://help.github.com/articles/using-pull-requests).
+
+Pull requests should be targeted at the `master` branch. Before creating a pull request, go through this checklist:
+
+1. Create a feature branch off of `master` so that changes do not get mixed up.
+2. If your PR adds new code, it should include tests covering the new code. If your PR fixes a bug, it should include a regression test.
+3. PRs that change user-facing behavior or the command-line interface must have associated documentation.
+4. All code comments and documentation are expected to have proper English grammar and punctuation.
+5. [Rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) your local changes against the `master` branch.
+6. Run the full project test suite with the `go test ./...` command and confirm that it passes (see [TESTING.md](TESTING.md) for details).
+7. Run `go fmt ./...`.
+8. You must agree to the [Developer Certificate of Origin](https://developercertificate.org/) for your contributions; use `git commit -s` ([detailed information here](https://wiki.linuxfoundation.org/dco)).
+
+When creating a PR:
+
+1. Your PR title should be descriptive, and follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification (start with `fix:`, `feat:`, or similar).
+2. Your PR commit message will be used as the commit message when your PR is merged. Update this field if your PR diverges during review.
+3. Your PR description should have details on what the PR does. If it fixes an existing issue, include a line like "Fixes #XXXX".
+
+When all of the tests are passing, maintainer(s) will be assigned to review and merge the PR. If you're having trouble getting tests to pass, feel free to tag in [MAINTAINERS](MAINTAINERS) for help, or ask in Slack (see [Communication](#communication) below).
+
+
+## Communication
+
+We use the [#tuf](https://cloud-native.slack.com/archives/C8NMD3QJ3) and [#go-tuf](https://cloud-native.slack.com/archives/C02D577GX54) channel on [CNCF Slack](https://slack.cncf.io/). You are welcome to drop in and ask questions, discuss bugs, etc.
+
+You might also be interested in the TUF community beyond go-tuf; good places to start include:
+
+- [TUF mailing list](https://groups.google.com/g/theupdateframework)
+- TUF community meetings (monthly; join the mailing list or watch the Slack channel to see invitations)
+
+
+## Pull Request Review Policy
+
+* Anyone is welcome to review any PR, whether they are a maintainer or not!
+* Maintainers should aim to turn around reviews within five business days; feel free to ping, or tag in specific maintainers if a PR is taking longer than that.
+* See [MAINTAINERS](MAINTAINERS) for the current list of maintainers.
+
+Maintainers should look in [MAINTAINERS.md](MAINTAINERS.md) for detailed quidelines.
diff --git a/docs/MAINTAINERS b/docs/MAINTAINERS
new file mode 100644
index 0000000..270e292
--- /dev/null
+++ b/docs/MAINTAINERS
@@ -0,0 +1,6 @@
+Trishank Karthik Kuppusamy <trishank.kuppusamy@datadoghq.com> (github: trishankatdatadog)
+Joshua Lock <jlock@vmware.com> (github: joshuagl)
+Marina Moore <mm9693@nyu.edu> (github: mnm678)
+Zack Newman <zjn@chainguard.dev> (github: znewman01)
+Radoslav Dimitrov <dimitrovr@vmware.com> (github: rdimitrov)
+Fredrik Skogman <kommendorkapten@github.com> (github: kommendorkapten)
diff --git a/docs/MAINTAINERS.md b/docs/MAINTAINERS.md
new file mode 100644
index 0000000..eb8d8a0
--- /dev/null
+++ b/docs/MAINTAINERS.md
@@ -0,0 +1,57 @@
+# go-tuf maintainer guidelines
+
+These are expectations for the [MAINTAINERS](MAINTAINERS) of go-tuf; if you are not able to meet these requirements, please remove yourself from the list of maintainers.
+
+## Process
+
+Speedy communication makes contributors happy!
+
+- You should get notifications for all activity in this repository (using the "Watch" feature) and quickly triage each issue/PR as it comes in.
+ - (non-draft) PRs should have assigned reviewers.
+ - Important bugs and questions should have assignees.
+- If you are assigned to review a PR, please try to *acknowledge* it within one business day (no need if you are OOO).
+- Please review all PRs within five business days (of course, it's okay if you're OOO).
+- Please use the review checklist below.
+- We should make sure there's an assigned reviewer for every PR which has passing tests.
+
+Versioning:
+
+- go-tuf releases follow [SemVer](https://semver.org/) with the following modification:
+ - While go-tuf is pre-1.0, increment the minor version for any breaking changes (in SemVer, there are no guarantees about API stability before 1.0).
+- Releases should be tagged in this repository as usual in Go (e.g. `v0.3.1`; see [Publishing a module](https://go.dev/doc/modules/publishing)).
+ - All maintainers should have permissions to push an appropriately-named `tag`, which will trigger the full release process.
+ - A patch release can happen at any point, but give the other maintainers 1 day's notice via Slack or GitHub first.
+ - For a minor release, see the "Project management" tag below.
+ - We may revisit this policy post-1.0.
+
+Project management:
+
+- Try to keep issues up-to-date with status updates!
+ - Feel free to ping open issues to check on them.
+ - Use the "assignee" field to indicate when you are working on an issue.
+ - Use GitHub issue labels to describe the issue (exact labels are still changing, so just look through and add those that seem like a good fit).
+- Before publishing a new minor release, there should be an associated [GitHub project](https://github.com/theupdateframework/go-tuf/projects?type=beta) to track issues.
+- We will develop more process around project management after we get through the v0.4.0 release.
+
+## Review checklist
+
+Code review:
+
+- [ ] Tests pass (enforced by CI).
+- [ ] There should be tests for any new functionality, and regression tests for any bugs.
+- [ ] Any user-facing functionality changes/additions (public APIs, command-line interface) should be documented.
+- [ ] Changes should be compliant with the [TUF specification](https://theupdateframework.github.io/specification/latest/).
+
+Pre-merge (check everything again before hitting the merge button!):
+
+- [ ] Approvals from two different organizations.
+ - This is *not* currently enforced by CI, though PRs must have at least 2 approvals.
+ - This may be waived for PRs which only update docs or comments, or trivial changes to tests.
+- Make sure that the PR title, commit message, and description are updated if the PR changes significantly during review.
+
+New version of the TUF specification:
+
+- There's an automated workflow which monitors and opens an issue in case there's newer version of the [TUF specification](https://theupdateframework.github.io/specification/latest/)
+- Closing the issue should happen after completing the following steps:
+ - Review the changes to the specification and make sure they're addressed (possibly requires breaking out a few relevant issues).
+ - Bump the `tuf-version` in the `.github/workflows/specification-version-check.yml` workflow.
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
new file mode 100644
index 0000000..eb26fed
--- /dev/null
+++ b/docs/SECURITY.md
@@ -0,0 +1,21 @@
+# Security Policy
+
+The go-tuf community is committed to maintaining a reliable and consistent TUF client implementation. If you believe you have identified a security issue in go-tuf's client protocol, please follow these guidelines for responsible disclosure.
+
+## Supported Versions
+
+You may report issues for the most recent version of go-tuf. We will not retroactively make changes to older versions.
+
+## Reporting a Vulnerability
+
+If you discover a potential security issue in this project we ask that you notify the go-tuf maintainers via [Github's private reporting feature](https://github.com/theupdateframework/go-tuf/security/advisories/new) (requires being signed in to GitHub). At the minimum, the report must contain the following:
+
+* A description of the issue.
+* A specific version or commit SHA of `go-tuf` where the issue reproduces.
+* Instructions to reproduce the issue.
+
+Please do **not** create a public GitHub issue or pull request to submit vulnerability reports. These public trackers are intended for non-time-sensitive and non-security-related bug reports and feature requests. Major feature requests, such as design changes to the specification, should be proposed via a [TUF Augmentation Protocol](https://theupdateframework.github.io/specification/latest/#tuf-augmentation-proposal-tap-support) (TAP).
+
+## Disclosure
+
+This project follows a 90 day disclosure timeline.
diff --git a/docs/TESTING.md b/docs/TESTING.md
new file mode 100644
index 0000000..5549853
--- /dev/null
+++ b/docs/TESTING.md
@@ -0,0 +1,18 @@
+# Testing
+
+The [Python interoperability tests](../client/python_interop/) require Python 3
+(available as `python` on the `$PATH`) and the [`python-tuf`
+package](https://github.com/theupdateframework/python-tuf) installed. To use the correct versions of the packages, it is recommended to use a [virtual environment](https://docs.python.org/3/library/venv.html#module-venv) and install the dependencies via:
+
+```
+python -m pip install --upgrade -r requirements-test.txt
+```
+
+You may run the full set of tests using
+```
+go test ./...
+```
+
+
+To update the data for these tests requires Docker and make (see
+test data [README.md](../client/python_interop/testdata/README.md) for details).
diff --git a/docs/audit/X41-go-tuf-Audit-2023-Final-Report-PUBLIC.pdf b/docs/audit/X41-go-tuf-Audit-2023-Final-Report-PUBLIC.pdf
new file mode 100644
index 0000000..0ac12c3
--- /dev/null
+++ b/docs/audit/X41-go-tuf-Audit-2023-Final-Report-PUBLIC.pdf
Binary files differ
diff --git a/docs/audit/blog-post.md b/docs/audit/blog-post.md
new file mode 100644
index 0000000..2b6d127
--- /dev/null
+++ b/docs/audit/blog-post.md
@@ -0,0 +1,33 @@
+# Security audit of go-tuf
+
+## The Update Framework
+
+## Radoslav Dimitrov - 06/13/2023
+
+go-tuf, a Go implementation of The Update Framework (TUF), plays a critical role in securely managing
+software updates. Recently, a security assessment conducted by X41 D-Sec GmbH uncovered a few
+security-related findings in go-tuf. The following blog post provides a concise overview of the
+assessment's key findings and recommendations.
+
+The assessment identified two vulnerabilities of medium and low severity in go-tuf, along with
+four additional issues without immediate security implications. Even though there are no
+high-severity issues, these vulnerabilities still pose risks in update integrity and local file
+overwrite scenarios.
+
+To address the vulnerabilities, it was recommended to implement measures such as linking versions
+and making root metadata project-specific. Additionally, validating signatures for all files
+processed by go-tuf will enhance protection against file manipulation during transit. Alongside
+the identified vulnerabilities, the security assessment highlighted that the “keys” directory had
+been assigned with world-readable permissions. Although this does not directly expose key files to
+local attackers, it is considered a best practice to assign only the minimum necessary permissions.
+
+Despite the discovery of only a few vulnerabilities and weaknesses, go-tuf demonstrates a high level
+of maturity in terms of security. The security assessment conducted by X41 D-Sec GmbH underscores
+the importance of continuously improving the project's security posture and implementing best practices.
+Alongside maintaining the project, the maintainers of go-tuf are also working on a new code base
+which is based on the positive redesign of python-tuf. This new code base is designed to be more
+efficient, user-friendly, and easier to maintain. The ongoing efforts of the maintainers to improve
+the project's design and simplicity will contribute to the overall health and long-term success
+of go-tuf. By implementing the recommended measures, adhering to best practices, and leveraging
+the advancements in the new code base, go-tuf will continue to provide a reliable and secure solution
+for managing software updates.
diff --git a/encrypted/encrypted.go b/encrypted/encrypted.go
new file mode 100644
index 0000000..b884d61
--- /dev/null
+++ b/encrypted/encrypted.go
@@ -0,0 +1,294 @@
+// Package encrypted provides a simple, secure system for encrypting data
+// symmetrically with a passphrase.
+//
+// It uses scrypt derive a key from the passphrase and the NaCl secret box
+// cipher for authenticated encryption.
+//
+// Deprecated: The encrypted package from go-tuf is already moved to
+// https://github.com/secure-systems-lab/go-securesystemslib and will be deprecated here.
+// Use github.com/secure-systems-lab/go-securesystemslib/encrypted instead.
+package encrypted
+
+import (
+ "crypto/rand"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+
+ "golang.org/x/crypto/nacl/secretbox"
+ "golang.org/x/crypto/scrypt"
+)
+
+const saltSize = 32
+
+const (
+ boxKeySize = 32
+ boxNonceSize = 24
+)
+
+// KDFParameterStrength defines the KDF parameter strength level to be used for
+// encryption key derivation.
+type KDFParameterStrength uint8
+
+const (
+ // Legacy defines legacy scrypt parameters (N:2^15, r:8, p:1)
+ Legacy KDFParameterStrength = iota + 1
+ // Standard defines standard scrypt parameters which is focusing 100ms of computation (N:2^16, r:8, p:1)
+ Standard
+ // OWASP defines OWASP recommended scrypt parameters (N:2^17, r:8, p:1)
+ OWASP
+)
+
+var (
+ // legacyParams represents old scrypt derivation parameters for backward
+ // compatibility.
+ legacyParams = scryptParams{
+ N: 32768, // 2^15
+ R: 8,
+ P: 1,
+ }
+
+ // standardParams defines scrypt parameters based on the scrypt creator
+ // recommendation to limit key derivation in time boxed to 100ms.
+ standardParams = scryptParams{
+ N: 65536, // 2^16
+ R: 8,
+ P: 1,
+ }
+
+ // owaspParams defines scrypt parameters recommended by OWASP
+ owaspParams = scryptParams{
+ N: 131072, // 2^17
+ R: 8,
+ P: 1,
+ }
+
+ // defaultParams defines scrypt parameters which will be used to generate a
+ // new key.
+ defaultParams = standardParams
+)
+
+const (
+ nameScrypt = "scrypt"
+ nameSecretBox = "nacl/secretbox"
+)
+
+type data struct {
+ KDF scryptKDF `json:"kdf"`
+ Cipher secretBoxCipher `json:"cipher"`
+ Ciphertext []byte `json:"ciphertext"`
+}
+
+type scryptParams struct {
+ N int `json:"N"`
+ R int `json:"r"`
+ P int `json:"p"`
+}
+
+func (sp *scryptParams) Equal(in *scryptParams) bool {
+ return in != nil && sp.N == in.N && sp.P == in.P && sp.R == in.R
+}
+
+func newScryptKDF(level KDFParameterStrength) (scryptKDF, error) {
+ salt := make([]byte, saltSize)
+ if err := fillRandom(salt); err != nil {
+ return scryptKDF{}, fmt.Errorf("unable to generate a random salt: %w", err)
+ }
+
+ var params scryptParams
+ switch level {
+ case Legacy:
+ params = legacyParams
+ case Standard:
+ params = standardParams
+ case OWASP:
+ params = owaspParams
+ default:
+ // Fallback to default parameters
+ params = defaultParams
+ }
+
+ return scryptKDF{
+ Name: nameScrypt,
+ Params: params,
+ Salt: salt,
+ }, nil
+}
+
+type scryptKDF struct {
+ Name string `json:"name"`
+ Params scryptParams `json:"params"`
+ Salt []byte `json:"salt"`
+}
+
+func (s *scryptKDF) Key(passphrase []byte) ([]byte, error) {
+ return scrypt.Key(passphrase, s.Salt, s.Params.N, s.Params.R, s.Params.P, boxKeySize)
+}
+
+// CheckParams checks that the encoded KDF parameters are what we expect them to
+// be. If we do not do this, an attacker could cause a DoS by tampering with
+// them.
+func (s *scryptKDF) CheckParams() error {
+ switch {
+ case legacyParams.Equal(&s.Params):
+ case standardParams.Equal(&s.Params):
+ case owaspParams.Equal(&s.Params):
+ default:
+ return errors.New("unsupported scrypt parameters")
+ }
+
+ return nil
+}
+
+func newSecretBoxCipher() (secretBoxCipher, error) {
+ nonce := make([]byte, boxNonceSize)
+ if err := fillRandom(nonce); err != nil {
+ return secretBoxCipher{}, err
+ }
+ return secretBoxCipher{
+ Name: nameSecretBox,
+ Nonce: nonce,
+ }, nil
+}
+
+type secretBoxCipher struct {
+ Name string `json:"name"`
+ Nonce []byte `json:"nonce"`
+
+ encrypted bool
+}
+
+func (s *secretBoxCipher) Encrypt(plaintext, key []byte) []byte {
+ var keyBytes [boxKeySize]byte
+ var nonceBytes [boxNonceSize]byte
+
+ if len(key) != len(keyBytes) {
+ panic("incorrect key size")
+ }
+ if len(s.Nonce) != len(nonceBytes) {
+ panic("incorrect nonce size")
+ }
+
+ copy(keyBytes[:], key)
+ copy(nonceBytes[:], s.Nonce)
+
+ // ensure that we don't re-use nonces
+ if s.encrypted {
+ panic("Encrypt must only be called once for each cipher instance")
+ }
+ s.encrypted = true
+
+ return secretbox.Seal(nil, plaintext, &nonceBytes, &keyBytes)
+}
+
+func (s *secretBoxCipher) Decrypt(ciphertext, key []byte) ([]byte, error) {
+ var keyBytes [boxKeySize]byte
+ var nonceBytes [boxNonceSize]byte
+
+ if len(key) != len(keyBytes) {
+ panic("incorrect key size")
+ }
+ if len(s.Nonce) != len(nonceBytes) {
+ // return an error instead of panicking since the nonce is user input
+ return nil, errors.New("encrypted: incorrect nonce size")
+ }
+
+ copy(keyBytes[:], key)
+ copy(nonceBytes[:], s.Nonce)
+
+ res, ok := secretbox.Open(nil, ciphertext, &nonceBytes, &keyBytes)
+ if !ok {
+ return nil, errors.New("encrypted: decryption failed")
+ }
+ return res, nil
+}
+
+// Encrypt takes a passphrase and plaintext, and returns a JSON object
+// containing ciphertext and the details necessary to decrypt it.
+func Encrypt(plaintext, passphrase []byte) ([]byte, error) {
+ return EncryptWithCustomKDFParameters(plaintext, passphrase, Standard)
+}
+
+// EncryptWithCustomKDFParameters takes a passphrase, the plaintext and a KDF
+// parameter level (Legacy, Standard, or OWASP), and returns a JSON object
+// containing ciphertext and the details necessary to decrypt it.
+func EncryptWithCustomKDFParameters(plaintext, passphrase []byte, kdfLevel KDFParameterStrength) ([]byte, error) {
+ k, err := newScryptKDF(kdfLevel)
+ if err != nil {
+ return nil, err
+ }
+ key, err := k.Key(passphrase)
+ if err != nil {
+ return nil, err
+ }
+
+ c, err := newSecretBoxCipher()
+ if err != nil {
+ return nil, err
+ }
+
+ data := &data{
+ KDF: k,
+ Cipher: c,
+ }
+ data.Ciphertext = c.Encrypt(plaintext, key)
+
+ return json.Marshal(data)
+}
+
+// Marshal encrypts the JSON encoding of v using passphrase.
+func Marshal(v interface{}, passphrase []byte) ([]byte, error) {
+ return MarshalWithCustomKDFParameters(v, passphrase, Standard)
+}
+
+// MarshalWithCustomKDFParameters encrypts the JSON encoding of v using passphrase.
+func MarshalWithCustomKDFParameters(v interface{}, passphrase []byte, kdfLevel KDFParameterStrength) ([]byte, error) {
+ data, err := json.MarshalIndent(v, "", "\t")
+ if err != nil {
+ return nil, err
+ }
+ return EncryptWithCustomKDFParameters(data, passphrase, kdfLevel)
+}
+
+// Decrypt takes a JSON-encoded ciphertext object encrypted using Encrypt and
+// tries to decrypt it using passphrase. If successful, it returns the
+// plaintext.
+func Decrypt(ciphertext, passphrase []byte) ([]byte, error) {
+ data := &data{}
+ if err := json.Unmarshal(ciphertext, data); err != nil {
+ return nil, err
+ }
+
+ if data.KDF.Name != nameScrypt {
+ return nil, fmt.Errorf("encrypted: unknown kdf name %q", data.KDF.Name)
+ }
+ if data.Cipher.Name != nameSecretBox {
+ return nil, fmt.Errorf("encrypted: unknown cipher name %q", data.Cipher.Name)
+ }
+ if err := data.KDF.CheckParams(); err != nil {
+ return nil, err
+ }
+
+ key, err := data.KDF.Key(passphrase)
+ if err != nil {
+ return nil, err
+ }
+
+ return data.Cipher.Decrypt(data.Ciphertext, key)
+}
+
+// Unmarshal decrypts the data using passphrase and unmarshals the resulting
+// plaintext into the value pointed to by v.
+func Unmarshal(data []byte, v interface{}, passphrase []byte) error {
+ decrypted, err := Decrypt(data, passphrase)
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(decrypted, v)
+}
+
+func fillRandom(b []byte) error {
+ _, err := io.ReadFull(rand.Reader, b)
+ return err
+}
diff --git a/encrypted/encrypted_test.go b/encrypted/encrypted_test.go
new file mode 100644
index 0000000..bdffff9
--- /dev/null
+++ b/encrypted/encrypted_test.go
@@ -0,0 +1,158 @@
+package encrypted
+
+import (
+ "encoding/json"
+ "strings"
+ "testing"
+
+ . "gopkg.in/check.v1"
+)
+
+var (
+ kdfVectors = map[KDFParameterStrength][]byte{
+ Legacy: []byte(`{"kdf":{"name":"scrypt","params":{"N":32768,"r":8,"p":1},"salt":"WO3mVvyTwJ9vwT5/Tk5OW5WPIBUofMjcpEfrLnfY4uA="},"cipher":{"name":"nacl/secretbox","nonce":"tCy7HcTFr4uxv4Nrg/DWmncuZ148U1MX"},"ciphertext":"08n43p5G5yviPEZpO7tPPF4aZQkWiWjkv4taFdhDBA0tamKH4nw="}`),
+ Standard: []byte(`{"kdf":{"name":"scrypt","params":{"N":65536,"r":8,"p":1},"salt":"FhzPOt9/bJG4PTq6lQ6ecG6GzaOuOy/ynG5+yRiFlNs="},"cipher":{"name":"nacl/secretbox","nonce":"aw1ng1jHaDz/tQ7V2gR9O2+IGQ8xJEuE"},"ciphertext":"HycvuLZL4sYH0BrYTh4E/H20VtAW6u5zL5Pr+IBjYLYnCPzDkq8="}`),
+ OWASP: []byte(`{"kdf":{"name":"scrypt","params":{"N":131072,"r":8,"p":1},"salt":"m38E3kouJTtiheLQN22NQ8DTito5hrjpUIskqcd375k="},"cipher":{"name":"nacl/secretbox","nonce":"Y6PM13yA+o44pE/W1ZBwczeGnTV/m9Zc"},"ciphertext":"6H8sqj1K6B6yDjtH5AQ6lbFigg/C2yDDJc4rYJ79w9aVPImFIPI="}`),
+ }
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type EncryptedSuite struct{}
+
+var _ = Suite(&EncryptedSuite{})
+
+var plaintext = []byte("reallyimportant")
+
+func (EncryptedSuite) TestRoundtrip(c *C) {
+ passphrase := []byte("supersecret")
+
+ enc, err := Encrypt(plaintext, passphrase)
+ c.Assert(err, IsNil)
+
+ // successful decrypt
+ dec, err := Decrypt(enc, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(dec, DeepEquals, plaintext)
+
+ // wrong passphrase
+ passphrase[0] = 0
+ dec, err = Decrypt(enc, passphrase)
+ c.Assert(err, NotNil)
+ c.Assert(dec, IsNil)
+}
+
+func (EncryptedSuite) TestTamperedRoundtrip(c *C) {
+ passphrase := []byte("supersecret")
+
+ enc, err := Encrypt(plaintext, passphrase)
+ c.Assert(err, IsNil)
+
+ data := &data{}
+ err = json.Unmarshal(enc, data)
+ c.Assert(err, IsNil)
+
+ data.Ciphertext[0] = ^data.Ciphertext[0]
+
+ enc, _ = json.Marshal(data)
+
+ dec, err := Decrypt(enc, passphrase)
+ c.Assert(err, NotNil)
+ c.Assert(dec, IsNil)
+}
+
+func (EncryptedSuite) TestDecrypt(c *C) {
+ enc := []byte(`{"kdf":{"name":"scrypt","params":{"N":32768,"r":8,"p":1},"salt":"N9a7x5JFGbrtB2uBR81jPwp0eiLR4A7FV3mjVAQrg1g="},"cipher":{"name":"nacl/secretbox","nonce":"2h8HxMmgRfuYdpswZBQaU3xJ1nkA/5Ik"},"ciphertext":"SEW6sUh0jf2wfdjJGPNS9+bkk2uB+Cxamf32zR8XkQ=="}`)
+ passphrase := []byte("supersecret")
+
+ dec, err := Decrypt(enc, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(dec, DeepEquals, plaintext)
+}
+
+func (EncryptedSuite) TestMarshalUnmarshal(c *C) {
+ passphrase := []byte("supersecret")
+
+ wrapped, err := Marshal(plaintext, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(wrapped, NotNil)
+
+ var protected []byte
+ err = Unmarshal(wrapped, &protected, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(protected, DeepEquals, plaintext)
+}
+
+func (EncryptedSuite) TestInvalidKDFSettings(c *C) {
+ passphrase := []byte("supersecret")
+
+ wrapped, err := MarshalWithCustomKDFParameters(plaintext, passphrase, 0)
+ c.Assert(err, IsNil)
+ c.Assert(wrapped, NotNil)
+
+ var protected []byte
+ err = Unmarshal(wrapped, &protected, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(protected, DeepEquals, plaintext)
+}
+
+func (EncryptedSuite) TestLegacyKDFSettings(c *C) {
+ passphrase := []byte("supersecret")
+
+ wrapped, err := MarshalWithCustomKDFParameters(plaintext, passphrase, Legacy)
+ c.Assert(err, IsNil)
+ c.Assert(wrapped, NotNil)
+
+ var protected []byte
+ err = Unmarshal(wrapped, &protected, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(protected, DeepEquals, plaintext)
+}
+
+func (EncryptedSuite) TestStandardKDFSettings(c *C) {
+ passphrase := []byte("supersecret")
+
+ wrapped, err := MarshalWithCustomKDFParameters(plaintext, passphrase, Standard)
+ c.Assert(err, IsNil)
+ c.Assert(wrapped, NotNil)
+
+ var protected []byte
+ err = Unmarshal(wrapped, &protected, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(protected, DeepEquals, plaintext)
+}
+
+func (EncryptedSuite) TestOWASPKDFSettings(c *C) {
+ passphrase := []byte("supersecret")
+
+ wrapped, err := MarshalWithCustomKDFParameters(plaintext, passphrase, OWASP)
+ c.Assert(err, IsNil)
+ c.Assert(wrapped, NotNil)
+
+ var protected []byte
+ err = Unmarshal(wrapped, &protected, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(protected, DeepEquals, plaintext)
+}
+
+func (EncryptedSuite) TestKDFSettingVectors(c *C) {
+ passphrase := []byte("supersecret")
+
+ for _, v := range kdfVectors {
+ var protected []byte
+ err := Unmarshal(v, &protected, passphrase)
+ c.Assert(err, IsNil)
+ c.Assert(protected, DeepEquals, plaintext)
+ }
+}
+
+func (EncryptedSuite) TestUnsupportedKDFParameters(c *C) {
+ enc := []byte(`{"kdf":{"name":"scrypt","params":{"N":99,"r":99,"p":99},"salt":"cZFcQJdwPhPyhU1R4qkl0qVOIjZd4V/7LYYAavq166k="},"cipher":{"name":"nacl/secretbox","nonce":"7vhRS7j0hEPBWV05skAdgLj81AkGeE7U"},"ciphertext":"6WYU/YSXVbYzl/NzaeAzmjLyfFhOOjLc0d8/GFV0aBFdJvyCcXc="}`)
+ passphrase := []byte("supersecret")
+
+ dec, err := Decrypt(enc, passphrase)
+ c.Assert(err, NotNil)
+ c.Assert(dec, IsNil)
+ c.Assert(strings.Contains(err.Error(), "unsupported scrypt parameters"), Equals, true)
+}
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..0051c43
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,98 @@
+package tuf
+
+import (
+ "errors"
+ "fmt"
+ "time"
+)
+
+var (
+ ErrInitNotAllowed = errors.New("tuf: repository already initialized")
+ ErrNewRepository = errors.New("tuf: repository not yet committed")
+ ErrChangePassphraseNotSupported = errors.New("tuf: store does not support changing passphrase")
+)
+
+type ErrMissingMetadata struct {
+ Name string
+}
+
+func (e ErrMissingMetadata) Error() string {
+ return fmt.Sprintf("tuf: missing metadata file %s", e.Name)
+}
+
+type ErrFileNotFound struct {
+ Path string
+}
+
+func (e ErrFileNotFound) Error() string {
+ return fmt.Sprintf("tuf: file not found %s", e.Path)
+}
+
+type ErrNoKeys struct {
+ Name string
+}
+
+func (e ErrNoKeys) Error() string {
+ return fmt.Sprintf("tuf: no keys available to sign %s", e.Name)
+}
+
+type ErrInsufficientSignatures struct {
+ Name string
+ Err error
+}
+
+func (e ErrInsufficientSignatures) Error() string {
+ return fmt.Sprintf("tuf: insufficient signatures for %s: %s", e.Name, e.Err)
+}
+
+type ErrInvalidRole struct {
+ Role string
+ Reason string
+}
+
+func (e ErrInvalidRole) Error() string {
+ return fmt.Sprintf("tuf: invalid role %s: %s", e.Role, e.Reason)
+}
+
+type ErrInvalidExpires struct {
+ Expires time.Time
+}
+
+func (e ErrInvalidExpires) Error() string {
+ return fmt.Sprintf("tuf: invalid expires: %s", e.Expires)
+}
+
+type ErrKeyNotFound struct {
+ Role string
+ KeyID string
+}
+
+func (e ErrKeyNotFound) Error() string {
+ return fmt.Sprintf(`tuf: no key with id "%s" exists for the %s role`, e.KeyID, e.Role)
+}
+
+type ErrNotEnoughKeys struct {
+ Role string
+ Keys int
+ Threshold int
+}
+
+func (e ErrNotEnoughKeys) Error() string {
+ return fmt.Sprintf("tuf: %s role has insufficient keys for threshold (has %d keys, threshold is %d)", e.Role, e.Keys, e.Threshold)
+}
+
+type ErrPassphraseRequired struct {
+ Role string
+}
+
+func (e ErrPassphraseRequired) Error() string {
+ return fmt.Sprintf("tuf: a passphrase is required to access the encrypted %s keys file", e.Role)
+}
+
+type ErrNoDelegatedTarget struct {
+ Path string
+}
+
+func (e ErrNoDelegatedTarget) Error() string {
+ return fmt.Sprintf("tuf: no delegated target for path %s", e.Path)
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..357173d
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,25 @@
+module github.com/theupdateframework/go-tuf
+
+go 1.20
+
+require (
+ github.com/dustin/go-humanize v1.0.1
+ github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e
+ github.com/google/gofuzz v1.2.0
+ github.com/secure-systems-lab/go-securesystemslib v0.7.0
+ github.com/stretchr/testify v1.8.4
+ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
+ golang.org/x/crypto v0.12.0
+ golang.org/x/term v0.11.0
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/kr/pretty v0.2.1 // indirect
+ github.com/kr/text v0.1.0 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ golang.org/x/sys v0.11.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..2bafca6
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,140 @@
+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/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.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+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/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
+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/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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+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.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.1.3/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.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
+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.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg=
+github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI=
+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.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
+github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
+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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+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-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-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-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+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-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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/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/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
+golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+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=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+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/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.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/fsutil/fsutil.go b/internal/fsutil/fsutil.go
new file mode 100644
index 0000000..138f210
--- /dev/null
+++ b/internal/fsutil/fsutil.go
@@ -0,0 +1,23 @@
+// Package fsutil defiens a set of internal utility functions used to
+// interact with the file system.
+package fsutil
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+// IsMetaFile tests wheter a DirEntry appears to be a metadata file or not.
+func IsMetaFile(e os.DirEntry) (bool, error) {
+ if e.IsDir() || filepath.Ext(e.Name()) != ".json" {
+ return false, nil
+ }
+
+ info, err := e.Info()
+ if err != nil {
+ return false, fmt.Errorf("error retrieving FileInfo for %s: %w", e.Name(), err)
+ }
+
+ return info.Mode().IsRegular(), nil
+}
diff --git a/internal/fsutil/perm.go b/internal/fsutil/perm.go
new file mode 100644
index 0000000..f94add6
--- /dev/null
+++ b/internal/fsutil/perm.go
@@ -0,0 +1,30 @@
+//go:build !windows
+// +build !windows
+
+package fsutil
+
+import (
+ "fmt"
+ "os"
+)
+
+// EnsureMaxPermissions tests the provided file info, returning an error if the
+// file's permission bits contain excess permissions not set in maxPerms.
+//
+// For example, a file with permissions -rw------- will successfully validate
+// with maxPerms -rw-r--r-- or -rw-rw-r--, but will not validate with maxPerms
+// -r-------- (due to excess --w------- permission) or --w------- (due to
+// excess -r-------- permission).
+//
+// Only permission bits of the file modes are considered.
+func EnsureMaxPermissions(fi os.FileInfo, maxPerms os.FileMode) error {
+ gotPerm := fi.Mode().Perm()
+ forbiddenPerms := (^maxPerms).Perm()
+ excessPerms := gotPerm & forbiddenPerms
+
+ if excessPerms != 0 {
+ return fmt.Errorf("permission bits for file %v failed validation: want at most %v, got %v with excess perms %v", fi.Name(), maxPerms.Perm(), gotPerm, excessPerms)
+ }
+
+ return nil
+}
diff --git a/internal/fsutil/perm_test.go b/internal/fsutil/perm_test.go
new file mode 100644
index 0000000..f80ef94
--- /dev/null
+++ b/internal/fsutil/perm_test.go
@@ -0,0 +1,69 @@
+//go:build !windows
+// +build !windows
+
+package fsutil
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEnsureMaxPermissions(t *testing.T) {
+ tmp := t.TempDir()
+ p := filepath.Join(tmp, "file.txt")
+
+ // Start with 0644 and change using os.Chmod so umask doesn't interfere.
+ err := os.WriteFile(p, []byte(`AAA`), 0644)
+ assert.NoError(t, err)
+
+ // Check matching (1)
+ err = os.Chmod(p, 0464)
+ assert.NoError(t, err)
+ fi, err := os.Stat(p)
+ assert.NoError(t, err)
+ err = EnsureMaxPermissions(fi, os.FileMode(0464))
+ assert.NoError(t, err)
+
+ // Check matching (2)
+ err = os.Chmod(p, 0642)
+ assert.NoError(t, err)
+ fi, err = os.Stat(p)
+ assert.NoError(t, err)
+ err = EnsureMaxPermissions(fi, os.FileMode(0642))
+ assert.NoError(t, err)
+
+ // Check matching with file mode bits
+ err = os.Chmod(p, 0444)
+ assert.NoError(t, err)
+ fi, err = os.Stat(p)
+ assert.NoError(t, err)
+ err = EnsureMaxPermissions(fi, os.ModeSymlink|os.ModeAppend|os.FileMode(0444))
+ assert.NoError(t, err)
+
+ // Check not matching (1)
+ err = os.Chmod(p, 0444)
+ assert.NoError(t, err)
+ fi, err = os.Stat(p)
+ assert.NoError(t, err)
+ err = EnsureMaxPermissions(fi, os.FileMode(0400))
+ assert.Error(t, err)
+
+ // Check not matching (2)
+ err = os.Chmod(p, 0444)
+ assert.NoError(t, err)
+ fi, err = os.Stat(p)
+ assert.NoError(t, err)
+ err = EnsureMaxPermissions(fi, os.FileMode(0222))
+ assert.Error(t, err)
+
+ // Check matching due to more restrictive perms on file
+ err = os.Chmod(p, 0444)
+ assert.NoError(t, err)
+ fi, err = os.Stat(p)
+ assert.NoError(t, err)
+ err = EnsureMaxPermissions(fi, os.FileMode(0666))
+ assert.NoError(t, err)
+}
diff --git a/internal/fsutil/perm_windows.go b/internal/fsutil/perm_windows.go
new file mode 100644
index 0000000..cecfee6
--- /dev/null
+++ b/internal/fsutil/perm_windows.go
@@ -0,0 +1,17 @@
+package fsutil
+
+import (
+ "os"
+)
+
+// EnsureMaxPermissions tests the provided file info to make sure the
+// permission bits matches the provided.
+// On Windows system the permission bits are not really compatible with
+// UNIX-like permission bits. By setting the UNIX-like permission bits
+// on a Windows system only read/write by all users can be achieved.
+// See this issue for tracking and more details:
+// https://github.com/theupdateframework/go-tuf/issues/360
+// Currently this method will always return nil.
+func EnsureMaxPermissions(fi os.FileInfo, perm os.FileMode) error {
+ return nil
+}
diff --git a/internal/roles/roles.go b/internal/roles/roles.go
new file mode 100644
index 0000000..0b134b2
--- /dev/null
+++ b/internal/roles/roles.go
@@ -0,0 +1,48 @@
+package roles
+
+import (
+ "strconv"
+ "strings"
+)
+
+var TopLevelRoles = map[string]struct{}{
+ "root": {},
+ "targets": {},
+ "snapshot": {},
+ "timestamp": {},
+}
+
+func IsTopLevelRole(name string) bool {
+ _, ok := TopLevelRoles[name]
+ return ok
+}
+
+func IsDelegatedTargetsRole(name string) bool {
+ return !IsTopLevelRole(name)
+}
+
+func IsTopLevelManifest(name string) bool {
+ if IsVersionedManifest(name) {
+ var found bool
+ _, name, found = strings.Cut(name, ".")
+ if !found {
+ panic("expected a versioned manifest of the form x.role.json")
+ }
+ }
+ return IsTopLevelRole(strings.TrimSuffix(name, ".json"))
+}
+
+func IsDelegatedTargetsManifest(name string) bool {
+ return !IsTopLevelManifest(name)
+}
+
+func IsVersionedManifest(name string) bool {
+ parts := strings.Split(name, ".")
+ // Versioned manifests have the form "x.role.json"
+ if len(parts) < 3 {
+ return false
+ }
+
+ _, err := strconv.Atoi(parts[0])
+ return err == nil
+}
diff --git a/internal/roles/roles_test.go b/internal/roles/roles_test.go
new file mode 100644
index 0000000..8af1b02
--- /dev/null
+++ b/internal/roles/roles_test.go
@@ -0,0 +1,54 @@
+package roles
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestIsTopLevelRole(t *testing.T) {
+ assert.True(t, IsTopLevelRole("root"))
+ assert.True(t, IsTopLevelRole("targets"))
+ assert.True(t, IsTopLevelRole("timestamp"))
+ assert.True(t, IsTopLevelRole("snapshot"))
+ assert.False(t, IsTopLevelRole("bins"))
+}
+
+func TestIsDelegatedTargetsRole(t *testing.T) {
+ assert.False(t, IsDelegatedTargetsRole("root"))
+ assert.False(t, IsDelegatedTargetsRole("targets"))
+ assert.False(t, IsDelegatedTargetsRole("timestamp"))
+ assert.False(t, IsDelegatedTargetsRole("snapshot"))
+ assert.True(t, IsDelegatedTargetsRole("deleg"))
+}
+
+func TestIsTopLevelManifest(t *testing.T) {
+ assert.True(t, IsTopLevelManifest("root.json"))
+ assert.True(t, IsTopLevelManifest("1.root.json"))
+ assert.True(t, IsTopLevelManifest("targets.json"))
+ assert.True(t, IsTopLevelManifest("timestamp.json"))
+ assert.True(t, IsTopLevelManifest("snapshot.json"))
+ assert.True(t, IsTopLevelManifest("2.snapshot.json"))
+ assert.False(t, IsTopLevelManifest("bins.json"))
+ assert.False(t, IsTopLevelManifest("3.bins.json"))
+}
+
+func TestIsDelegatedTargetsManifest(t *testing.T) {
+ assert.False(t, IsDelegatedTargetsManifest("root.json"))
+ assert.False(t, IsDelegatedTargetsManifest("1.root.json"))
+ assert.False(t, IsDelegatedTargetsManifest("targets.json"))
+ assert.False(t, IsDelegatedTargetsManifest("2.targets.json"))
+ assert.False(t, IsDelegatedTargetsManifest("timestamp.json"))
+ assert.False(t, IsDelegatedTargetsManifest("snapshot.json"))
+ assert.True(t, IsDelegatedTargetsManifest("bins.json"))
+ assert.True(t, IsDelegatedTargetsManifest("2.bins.json"))
+}
+
+func TestIsVersionedManifest(t *testing.T) {
+ assert.False(t, IsVersionedManifest("a.b"))
+ assert.False(t, IsVersionedManifest("a.b.c"))
+ assert.False(t, IsVersionedManifest("a.b.json"))
+ assert.False(t, IsVersionedManifest("1.a"))
+ assert.True(t, IsVersionedManifest("1.a.json"))
+ assert.True(t, IsVersionedManifest("2.a.json"))
+}
diff --git a/internal/sets/strings.go b/internal/sets/strings.go
new file mode 100644
index 0000000..7eee57d
--- /dev/null
+++ b/internal/sets/strings.go
@@ -0,0 +1,24 @@
+package sets
+
+func StringSliceToSet(items []string) map[string]struct{} {
+ s := map[string]struct{}{}
+ for _, item := range items {
+ s[item] = struct{}{}
+ }
+ return s
+}
+
+func StringSetToSlice(items map[string]struct{}) []string {
+ ret := []string{}
+
+ for k := range items {
+ ret = append(ret, k)
+ }
+
+ return ret
+}
+
+func DeduplicateStrings(items []string) []string {
+ s := StringSliceToSet(items)
+ return StringSetToSlice(s)
+}
diff --git a/internal/sets/strings_test.go b/internal/sets/strings_test.go
new file mode 100644
index 0000000..6e0b91d
--- /dev/null
+++ b/internal/sets/strings_test.go
@@ -0,0 +1,35 @@
+package sets
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestStringSliceToSet(t *testing.T) {
+ assert.Equal(t,
+ map[string]struct{}{
+ "a": {},
+ "b": {},
+ "c": {},
+ },
+ StringSliceToSet([]string{"a", "c", "b", "c", "b"}))
+}
+
+func TestStringSetToSlice(t *testing.T) {
+ assert.ElementsMatch(t,
+ []string{"a", "b", "c"},
+ StringSetToSlice(map[string]struct{}{
+ "a": {},
+ "b": {},
+ "c": {},
+ }),
+ )
+}
+
+func TestDeduplicateStrings(t *testing.T) {
+ assert.ElementsMatch(t,
+ []string{"a", "b", "c"},
+ DeduplicateStrings([]string{"a", "c", "b", "c", "b"}),
+ )
+}
diff --git a/internal/signer/sort.go b/internal/signer/sort.go
new file mode 100644
index 0000000..777e201
--- /dev/null
+++ b/internal/signer/sort.go
@@ -0,0 +1,49 @@
+package signer
+
+import (
+ "sort"
+
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+// ByIDs implements sort.Interface for []keys.Signer based on
+// the sorted public IDs() for each Signer. This facilitates
+// deterministic order of signatures, which prevents tests
+// that use fixtures from being flaky.
+type ByIDs []keys.Signer
+
+func (b ByIDs) Len() int {
+ return len(b)
+}
+
+func (b ByIDs) Swap(i, j int) {
+ b[i], b[j] = b[j], b[i]
+}
+
+func (b ByIDs) Less(i, j int) bool {
+ ids := b[i].PublicData().IDs()
+ iIDs := make([]string, len(ids))
+ copy(iIDs, ids)
+ sort.Strings(iIDs)
+
+ ids = b[j].PublicData().IDs()
+ jIDs := make([]string, len(ids))
+ copy(jIDs, ids)
+ sort.Strings(jIDs)
+
+ minLen := len(iIDs)
+ if len(jIDs) < minLen {
+ minLen = len(jIDs)
+ }
+
+ // Compare iIDs[:minLen] to jIDs[:minLen] element-wise.
+ for c := 0; c < minLen; c++ {
+ if iIDs[c] == jIDs[c] {
+ continue
+ }
+ return iIDs[c] < jIDs[c]
+ }
+
+ // iIDs[:minLen] is equal to jIDs[:minLen], so sort based on length.
+ return len(iIDs) < len(jIDs)
+}
diff --git a/internal/signer/sort_test.go b/internal/signer/sort_test.go
new file mode 100644
index 0000000..afda5f8
--- /dev/null
+++ b/internal/signer/sort_test.go
@@ -0,0 +1,73 @@
+package signer_test
+
+import (
+ "encoding/json"
+ "sort"
+ "testing"
+
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/internal/signer"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+type mockSigner struct {
+ value json.RawMessage
+}
+
+func (s *mockSigner) MarshalPrivateKey() (*data.PrivateKey, error) {
+ panic("not implemented")
+}
+
+func (s *mockSigner) UnmarshalPrivateKey(key *data.PrivateKey) error {
+ panic("not implemented")
+}
+
+func (s *mockSigner) PublicData() *data.PublicKey {
+ return &data.PublicKey{
+ Type: "mock",
+ Scheme: "mock",
+ Algorithms: []data.HashAlgorithm{"mock"},
+ Value: s.value,
+ }
+}
+func (s *mockSigner) SignMessage(message []byte) ([]byte, error) {
+ panic("not implemented")
+}
+
+func TestSignerSortByIDs(t *testing.T) {
+ s1 := &mockSigner{
+ value: json.RawMessage(`{"mock": 1}`),
+ }
+ s2 := &mockSigner{
+ value: json.RawMessage(`{"mock": 2}`),
+ }
+ s3 := &mockSigner{
+ value: json.RawMessage(`{"mock": 3}`),
+ }
+ s4 := &mockSigner{
+ value: json.RawMessage(`{"mock": 4}`),
+ }
+ s5 := &mockSigner{
+ value: json.RawMessage(`{"mock": 5}`),
+ }
+
+ s := []keys.Signer{
+ s1, s2, s3, s4, s5,
+ }
+
+ sort.Sort(signer.ByIDs(s))
+
+ signerIDs := []string{}
+
+ for i, signer := range s {
+ ids := signer.PublicData().IDs()
+ if len(ids) != 1 {
+ t.Errorf("Signer %v IDs %v should have length 1", i, ids)
+ }
+ signerIDs = append(signerIDs, ids[0])
+ }
+
+ if !sort.StringsAreSorted(signerIDs) {
+ t.Errorf("Signers incorrectly sorted: %+v", signerIDs)
+ }
+}
diff --git a/local_store.go b/local_store.go
new file mode 100644
index 0000000..b59721e
--- /dev/null
+++ b/local_store.go
@@ -0,0 +1,741 @@
+package tuf
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/secure-systems-lab/go-securesystemslib/encrypted"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/internal/fsutil"
+ "github.com/theupdateframework/go-tuf/internal/sets"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+ "github.com/theupdateframework/go-tuf/util"
+)
+
+type LocalStore interface {
+ // GetMeta returns a map from metadata file names (e.g. root.json) to their raw JSON payload or an error.
+ GetMeta() (map[string]json.RawMessage, error)
+
+ // SetMeta is used to update a metadata file name with a JSON payload.
+ SetMeta(name string, meta json.RawMessage) error
+
+ // WalkStagedTargets calls targetsFn for each staged target file in paths.
+ // If paths is empty, all staged target files will be walked.
+ WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error
+
+ // FileIsStaged determines if a metadata file is currently staged, to avoid incrementing
+ // version numbers repeatedly while staged.
+ FileIsStaged(filename string) bool
+
+ // Commit is used to publish staged files to the repository
+ //
+ // This will also reset the staged meta to signal incrementing version numbers.
+ // TUF 1.0 requires that the root metadata version numbers in the repository does not
+ // gaps. To avoid this, we will only increment the number once until we commit.
+ Commit(bool, map[string]int64, map[string]data.Hashes) error
+
+ // GetSigners return a list of signers for a role.
+ // This may include revoked keys, so the signers should not
+ // be used without filtering.
+ GetSigners(role string) ([]keys.Signer, error)
+
+ // SaveSigner adds a signer to a role.
+ SaveSigner(role string, signer keys.Signer) error
+
+ // SignersForRole return a list of signing keys for a role.
+ SignersForKeyIDs(keyIDs []string) []keys.Signer
+
+ // Clean is used to remove all staged manifests.
+ Clean() error
+}
+
+type PassphraseChanger interface {
+ // ChangePassphrase changes the passphrase for a role keys file.
+ ChangePassphrase(string) error
+}
+
+func MemoryStore(meta map[string]json.RawMessage, files map[string][]byte) LocalStore {
+ if meta == nil {
+ meta = make(map[string]json.RawMessage)
+ }
+ return &memoryStore{
+ meta: meta,
+ stagedMeta: make(map[string]json.RawMessage),
+ files: files,
+ signerForKeyID: make(map[string]keys.Signer),
+ keyIDsForRole: make(map[string][]string),
+ }
+}
+
+type memoryStore struct {
+ meta map[string]json.RawMessage
+ stagedMeta map[string]json.RawMessage
+ files map[string][]byte
+
+ signerForKeyID map[string]keys.Signer
+ keyIDsForRole map[string][]string
+}
+
+func (m *memoryStore) GetMeta() (map[string]json.RawMessage, error) {
+ meta := make(map[string]json.RawMessage, len(m.meta)+len(m.stagedMeta))
+ for key, value := range m.meta {
+ meta[key] = value
+ }
+ for key, value := range m.stagedMeta {
+ meta[key] = value
+ }
+ return meta, nil
+}
+
+func (m *memoryStore) SetMeta(name string, meta json.RawMessage) error {
+ m.stagedMeta[name] = meta
+ return nil
+}
+
+func (m *memoryStore) FileIsStaged(name string) bool {
+ _, ok := m.stagedMeta[name]
+ return ok
+}
+
+func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error {
+ if len(paths) == 0 {
+ for path, data := range m.files {
+ if err := targetsFn(path, bytes.NewReader(data)); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ for _, path := range paths {
+ data, ok := m.files[path]
+ if !ok {
+ return ErrFileNotFound{path}
+ }
+ if err := targetsFn(path, bytes.NewReader(data)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (m *memoryStore) Commit(consistentSnapshot bool, versions map[string]int64, hashes map[string]data.Hashes) error {
+ for name, meta := range m.stagedMeta {
+ paths := computeMetadataPaths(consistentSnapshot, name, versions)
+ for _, path := range paths {
+ m.meta[path] = meta
+ }
+ // Remove from staged metadata.
+ // This will prompt incrementing version numbers again now that we've
+ // successfully committed the metadata to the local store.
+ delete(m.stagedMeta, name)
+ }
+ return nil
+}
+
+func (m *memoryStore) GetSigners(role string) ([]keys.Signer, error) {
+ keyIDs, ok := m.keyIDsForRole[role]
+ if ok {
+ return m.SignersForKeyIDs(keyIDs), nil
+ }
+
+ return nil, nil
+}
+
+func (m *memoryStore) SaveSigner(role string, signer keys.Signer) error {
+ keyIDs := signer.PublicData().IDs()
+
+ for _, keyID := range keyIDs {
+ m.signerForKeyID[keyID] = signer
+ }
+
+ mergedKeyIDs := sets.DeduplicateStrings(append(m.keyIDsForRole[role], keyIDs...))
+ m.keyIDsForRole[role] = mergedKeyIDs
+ return nil
+}
+
+func (m *memoryStore) SignersForKeyIDs(keyIDs []string) []keys.Signer {
+ signers := []keys.Signer{}
+ keyIDsSeen := map[string]struct{}{}
+
+ for _, keyID := range keyIDs {
+ signer, ok := m.signerForKeyID[keyID]
+ if !ok {
+ continue
+ }
+ addSigner := false
+
+ for _, skid := range signer.PublicData().IDs() {
+ if _, seen := keyIDsSeen[skid]; !seen {
+ addSigner = true
+ }
+
+ keyIDsSeen[skid] = struct{}{}
+ }
+
+ if addSigner {
+ signers = append(signers, signer)
+ }
+ }
+
+ return signers
+}
+
+func (m *memoryStore) Clean() error {
+ return nil
+}
+
+type persistedKeys struct {
+ Encrypted bool `json:"encrypted"`
+ Data json.RawMessage `json:"data"`
+}
+
+type StoreOpts struct {
+ Logger *log.Logger
+ PassFunc util.PassphraseFunc
+}
+
+func FileSystemStore(dir string, p util.PassphraseFunc) LocalStore {
+ return &fileSystemStore{
+ dir: dir,
+ passphraseFunc: p,
+ logger: log.New(io.Discard, "", 0),
+ signerForKeyID: make(map[string]keys.Signer),
+ keyIDsForRole: make(map[string][]string),
+ }
+}
+
+func FileSystemStoreWithOpts(dir string, opts ...StoreOpts) LocalStore {
+ store := &fileSystemStore{
+ dir: dir,
+ passphraseFunc: nil,
+ logger: log.New(io.Discard, "", 0),
+ signerForKeyID: make(map[string]keys.Signer),
+ keyIDsForRole: make(map[string][]string),
+ }
+ for _, opt := range opts {
+ if opt.Logger != nil {
+ store.logger = opt.Logger
+ }
+ if opt.PassFunc != nil {
+ store.passphraseFunc = opt.PassFunc
+ }
+ }
+ return store
+}
+
+type fileSystemStore struct {
+ dir string
+ passphraseFunc util.PassphraseFunc
+ logger *log.Logger
+
+ signerForKeyID map[string]keys.Signer
+ keyIDsForRole map[string][]string
+}
+
+func (f *fileSystemStore) repoDir() string {
+ return filepath.Join(f.dir, "repository")
+}
+
+func (f *fileSystemStore) stagedDir() string {
+ return filepath.Join(f.dir, "staged")
+}
+
+func (f *fileSystemStore) GetMeta() (map[string]json.RawMessage, error) {
+ // Build a map of metadata names (e.g. root.json) to their full paths
+ // (whether in the committed repo dir, or in the staged repo dir).
+ metaPaths := map[string]string{}
+
+ rd := f.repoDir()
+ committed, err := os.ReadDir(f.repoDir())
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
+ return nil, fmt.Errorf("could not list repo dir: %w", err)
+ }
+
+ for _, e := range committed {
+ imf, err := fsutil.IsMetaFile(e)
+ if err != nil {
+ return nil, err
+ }
+ if imf {
+ name := e.Name()
+ metaPaths[name] = filepath.Join(rd, name)
+ }
+ }
+
+ sd := f.stagedDir()
+ staged, err := os.ReadDir(sd)
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
+ return nil, fmt.Errorf("could not list staged dir: %w", err)
+ }
+
+ for _, e := range staged {
+ imf, err := fsutil.IsMetaFile(e)
+ if err != nil {
+ return nil, err
+ }
+ if imf {
+ name := e.Name()
+ metaPaths[name] = filepath.Join(sd, name)
+ }
+ }
+
+ meta := make(map[string]json.RawMessage)
+ for name, path := range metaPaths {
+ f, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ meta[name] = f
+ }
+ return meta, nil
+}
+
+func (f *fileSystemStore) SetMeta(name string, meta json.RawMessage) error {
+ if err := f.createDirs(); err != nil {
+ return err
+ }
+ if err := util.AtomicallyWriteFile(filepath.Join(f.stagedDir(), name), meta, 0644); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (f *fileSystemStore) FileIsStaged(name string) bool {
+ _, err := os.Stat(filepath.Join(f.stagedDir(), name))
+ return err == nil
+}
+
+func (f *fileSystemStore) createDirs() error {
+ for _, dir := range []string{"keys", "repository", "staged/targets"} {
+ if err := os.MkdirAll(filepath.Join(f.dir, dir), 0755); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (f *fileSystemStore) WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error {
+ if len(paths) == 0 {
+ walkFunc := func(fpath string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if info.IsDir() || !info.Mode().IsRegular() {
+ return nil
+ }
+ rel, err := filepath.Rel(filepath.Join(f.stagedDir(), "targets"), fpath)
+ if err != nil {
+ return err
+ }
+ file, err := os.Open(fpath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ return targetsFn(filepath.ToSlash(rel), file)
+ }
+ return filepath.Walk(filepath.Join(f.stagedDir(), "targets"), walkFunc)
+ }
+
+ // check all the files exist before processing any files
+ for _, path := range paths {
+ realFilepath := filepath.Join(f.stagedDir(), "targets", path)
+ if _, err := os.Stat(realFilepath); err != nil {
+ if os.IsNotExist(err) {
+ return ErrFileNotFound{realFilepath}
+ }
+ return err
+ }
+ }
+
+ for _, path := range paths {
+ realFilepath := filepath.Join(f.stagedDir(), "targets", path)
+ file, err := os.Open(realFilepath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return ErrFileNotFound{realFilepath}
+ }
+ return err
+ }
+ err = targetsFn(path, file)
+ file.Close()
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (f *fileSystemStore) createRepoFile(path string) (*os.File, error) {
+ dst := filepath.Join(f.repoDir(), path)
+ if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
+ return nil, err
+ }
+ return os.Create(dst)
+}
+
+func (f *fileSystemStore) Commit(consistentSnapshot bool, versions map[string]int64, hashes map[string]data.Hashes) error {
+ isTarget := func(path string) bool {
+ return strings.HasPrefix(path, "targets/")
+ }
+ copyToRepo := func(fpath string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if info.IsDir() || !info.Mode().IsRegular() {
+ return nil
+ }
+ rel, err := filepath.Rel(f.stagedDir(), fpath)
+ if err != nil {
+ return err
+ }
+ relpath := filepath.ToSlash(rel)
+
+ var paths []string
+ if isTarget(relpath) {
+ paths = computeTargetPaths(consistentSnapshot, relpath, hashes)
+ } else {
+ paths = computeMetadataPaths(consistentSnapshot, relpath, versions)
+ }
+ var files []io.Writer
+ for _, path := range paths {
+ file, err := f.createRepoFile(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ files = append(files, file)
+ }
+ staged, err := os.Open(fpath)
+ if err != nil {
+ return err
+ }
+ defer staged.Close()
+ if _, err = io.Copy(io.MultiWriter(files...), staged); err != nil {
+ return err
+ }
+ return nil
+ }
+ // Checks if target file should be deleted
+ needsRemoval := func(fpath string) bool {
+ if consistentSnapshot {
+ // strip out the hash
+ name := strings.SplitN(filepath.Base(fpath), ".", 2)
+ if len(name) != 2 || name[1] == "" {
+ return false
+ }
+ fpath = filepath.Join(filepath.Dir(fpath), name[1])
+ }
+ _, ok := hashes[filepath.ToSlash(fpath)]
+ return !ok
+ }
+ // Checks if folder is empty
+ folderNeedsRemoval := func(fpath string) bool {
+ f, err := os.Open(fpath)
+ if err != nil {
+ return false
+ }
+ defer f.Close()
+ _, err = f.Readdirnames(1)
+ return err == io.EOF
+ }
+ removeFile := func(fpath string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ rel, err := filepath.Rel(f.repoDir(), fpath)
+ if err != nil {
+ return err
+ }
+ relpath := filepath.ToSlash(rel)
+ if !info.IsDir() && isTarget(relpath) && needsRemoval(rel) {
+ // Delete the target file
+ if err := os.Remove(fpath); err != nil {
+ return err
+ }
+ // Delete the target folder too if it's empty
+ targetFolder := filepath.Dir(fpath)
+ if folderNeedsRemoval(targetFolder) {
+ if err := os.Remove(targetFolder); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+ }
+ if err := filepath.Walk(f.stagedDir(), copyToRepo); err != nil {
+ return err
+ }
+ if err := filepath.Walk(f.repoDir(), removeFile); err != nil {
+ return err
+ }
+ return f.Clean()
+}
+
+func (f *fileSystemStore) GetSigners(role string) ([]keys.Signer, error) {
+ keyIDs, ok := f.keyIDsForRole[role]
+ if ok {
+ return f.SignersForKeyIDs(keyIDs), nil
+ }
+
+ privKeys, _, err := f.loadPrivateKeys(role)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+ return nil, err
+ }
+
+ signers := []keys.Signer{}
+ for _, key := range privKeys {
+ signer, err := keys.GetSigner(key)
+ if err != nil {
+ return nil, err
+ }
+
+ // Cache the signers.
+ for _, keyID := range signer.PublicData().IDs() {
+ f.keyIDsForRole[role] = append(f.keyIDsForRole[role], keyID)
+ f.signerForKeyID[keyID] = signer
+ }
+ signers = append(signers, signer)
+ }
+
+ return signers, nil
+}
+
+func (f *fileSystemStore) SignersForKeyIDs(keyIDs []string) []keys.Signer {
+ signers := []keys.Signer{}
+ keyIDsSeen := map[string]struct{}{}
+
+ for _, keyID := range keyIDs {
+ signer, ok := f.signerForKeyID[keyID]
+ if !ok {
+ continue
+ }
+
+ addSigner := false
+
+ for _, skid := range signer.PublicData().IDs() {
+ if _, seen := keyIDsSeen[skid]; !seen {
+ addSigner = true
+ }
+
+ keyIDsSeen[skid] = struct{}{}
+ }
+
+ if addSigner {
+ signers = append(signers, signer)
+ }
+ }
+
+ return signers
+}
+
+// ChangePassphrase changes the passphrase for a role keys file. Implements
+// PassphraseChanger interface.
+func (f *fileSystemStore) ChangePassphrase(role string) error {
+ // No need to proceed if passphrase func is not set
+ if f.passphraseFunc == nil {
+ return ErrPassphraseRequired{role}
+ }
+ // Read the existing keys (if any)
+ // If encrypted, will prompt for existing passphrase
+ keys, _, err := f.loadPrivateKeys(role)
+ if err != nil {
+ if os.IsNotExist(err) {
+ f.logger.Printf("Failed to change passphrase. Missing keys file for %s role. \n", role)
+ }
+ return err
+ }
+ // Prompt for new passphrase
+ pass, err := f.passphraseFunc(role, true, true)
+ if err != nil {
+ return err
+ }
+ // Proceed saving the keys
+ pk := &persistedKeys{Encrypted: true}
+ pk.Data, err = encrypted.Marshal(keys, pass)
+ if err != nil {
+ return err
+ }
+ data, err := json.MarshalIndent(pk, "", "\t")
+ if err != nil {
+ return err
+ }
+ if err := util.AtomicallyWriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil {
+ return err
+ }
+ f.logger.Printf("Successfully changed passphrase for %s keys file\n", role)
+ return nil
+}
+
+func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error {
+ if err := f.createDirs(); err != nil {
+ return err
+ }
+
+ // add the key to the existing keys (if any)
+ privKeys, pass, err := f.loadPrivateKeys(role)
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ key, err := signer.MarshalPrivateKey()
+ if err != nil {
+ return err
+ }
+ privKeys = append(privKeys, key)
+
+ // if loadPrivateKeys didn't return a passphrase (because no keys yet exist)
+ // and passphraseFunc is set, get the passphrase so the keys file can
+ // be encrypted later (passphraseFunc being nil indicates the keys file
+ // should not be encrypted)
+ if pass == nil && f.passphraseFunc != nil {
+ pass, err = f.passphraseFunc(role, true, false)
+ if err != nil {
+ return err
+ }
+ }
+
+ pk := &persistedKeys{}
+ if pass != nil {
+ pk.Data, err = encrypted.Marshal(privKeys, pass)
+ if err != nil {
+ return err
+ }
+ pk.Encrypted = true
+ } else {
+ pk.Data, err = json.MarshalIndent(privKeys, "", "\t")
+ if err != nil {
+ return err
+ }
+ }
+ data, err := json.MarshalIndent(pk, "", "\t")
+ if err != nil {
+ return err
+ }
+ if err := util.AtomicallyWriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil {
+ return err
+ }
+
+ // Merge privKeys into f.keyIDsForRole and register signers with
+ // f.signerForKeyID.
+ keyIDsForRole := f.keyIDsForRole[role]
+ for _, key := range privKeys {
+ signer, err := keys.GetSigner(key)
+ if err != nil {
+ return err
+ }
+
+ keyIDs := signer.PublicData().IDs()
+
+ for _, keyID := range keyIDs {
+ f.signerForKeyID[keyID] = signer
+ }
+
+ keyIDsForRole = append(keyIDsForRole, keyIDs...)
+ }
+
+ f.keyIDsForRole[role] = sets.DeduplicateStrings(keyIDsForRole)
+
+ return nil
+}
+
+// loadPrivateKeys loads keys for the given role and returns them along with the
+// passphrase (if read) so that callers don't need to re-read it.
+func (f *fileSystemStore) loadPrivateKeys(role string) ([]*data.PrivateKey, []byte, error) {
+ file, err := os.Open(f.keysPath(role))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer file.Close()
+
+ pk := &persistedKeys{}
+ if err := json.NewDecoder(file).Decode(pk); err != nil {
+ return nil, nil, err
+ }
+
+ var keys []*data.PrivateKey
+ if !pk.Encrypted {
+ if err := json.Unmarshal(pk.Data, &keys); err != nil {
+ return nil, nil, err
+ }
+ return keys, nil, nil
+ }
+
+ // the keys are encrypted so cannot be loaded if passphraseFunc is not set
+ if f.passphraseFunc == nil {
+ return nil, nil, ErrPassphraseRequired{role}
+ }
+
+ // try the empty string as the password first
+ pass := []byte("")
+ if err := encrypted.Unmarshal(pk.Data, &keys, pass); err != nil {
+ pass, err = f.passphraseFunc(role, false, false)
+ if err != nil {
+ return nil, nil, err
+ }
+ if err = encrypted.Unmarshal(pk.Data, &keys, pass); err != nil {
+ return nil, nil, err
+ }
+ }
+ return keys, pass, nil
+}
+
+func (f *fileSystemStore) keysPath(role string) string {
+ return filepath.Join(f.dir, "keys", role+".json")
+}
+
+func (f *fileSystemStore) Clean() error {
+ _, err := os.Stat(filepath.Join(f.repoDir(), "root.json"))
+ if os.IsNotExist(err) {
+ return ErrNewRepository
+ } else if err != nil {
+ return err
+ }
+ if err := os.RemoveAll(f.stagedDir()); err != nil {
+ return err
+ }
+ return os.MkdirAll(filepath.Join(f.stagedDir(), "targets"), 0755)
+}
+
+func computeTargetPaths(consistentSnapshot bool, name string, hashes map[string]data.Hashes) []string {
+ if consistentSnapshot {
+ return util.HashedPaths(name, hashes[name])
+ } else {
+ return []string{name}
+ }
+}
+
+func computeMetadataPaths(consistentSnapshot bool, name string, versions map[string]int64) []string {
+ copyVersion := false
+
+ switch name {
+ case "root.json":
+ copyVersion = true
+ case "timestamp.json":
+ copyVersion = false
+ default:
+ if consistentSnapshot {
+ copyVersion = true
+ } else {
+ copyVersion = false
+ }
+ }
+
+ paths := []string{name}
+ if copyVersion {
+ paths = append(paths, util.VersionedPath(name, versions[name]))
+ }
+
+ return paths
+}
diff --git a/local_store_test.go b/local_store_test.go
new file mode 100644
index 0000000..8badcfc
--- /dev/null
+++ b/local_store_test.go
@@ -0,0 +1,70 @@
+package tuf
+
+import (
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+func TestLocalStoreSigners(t *testing.T) {
+ tmpdir, err := os.MkdirTemp("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ os.RemoveAll(tmpdir)
+ }()
+
+ stores := map[string]LocalStore{
+ "MemoryStore": MemoryStore(nil, nil),
+ "FileSystemStore": FileSystemStore(tmpdir, nil),
+ }
+
+ for name, store := range stores {
+ t.Run(name, func(t *testing.T) {
+ signers, err := store.GetSigners("abc")
+ assert.NoError(t, err)
+ assert.Equal(t, len(signers), 0)
+
+ // Add two signers to role "a".
+ aSigner1, err := keys.GenerateEd25519Key()
+ assert.NoError(t, err)
+ err = store.SaveSigner("a", aSigner1)
+ assert.NoError(t, err)
+
+ aSigner2, err := keys.GenerateEd25519Key()
+ assert.NoError(t, err)
+ err = store.SaveSigner("a", aSigner2)
+ assert.NoError(t, err)
+
+ // Add one signer to role "b".
+ bSigner, err := keys.GenerateEd25519Key()
+ assert.NoError(t, err)
+ err = store.SaveSigner("b", bSigner)
+ assert.NoError(t, err)
+
+ // Add to b again to test deduplication.
+ err = store.SaveSigner("b", bSigner)
+ assert.NoError(t, err)
+
+ signers, err = store.GetSigners("a")
+ assert.NoError(t, err)
+ assert.ElementsMatch(t, []keys.Signer{aSigner1, aSigner2}, signers)
+
+ signers, err = store.GetSigners("b")
+ assert.NoError(t, err)
+ assert.ElementsMatch(t, []keys.Signer{bSigner}, signers)
+
+ a1KeyIDs := aSigner1.PublicData().IDs()
+ a2KeyIDs := aSigner2.PublicData().IDs()
+ bKeyIDs := bSigner.PublicData().IDs()
+
+ assert.Equal(t, []keys.Signer{aSigner1}, store.SignersForKeyIDs(a1KeyIDs))
+ assert.Equal(t, []keys.Signer{aSigner2}, store.SignersForKeyIDs(a2KeyIDs))
+ assert.ElementsMatch(t, []keys.Signer{aSigner1, aSigner2}, store.SignersForKeyIDs(append(a1KeyIDs, a2KeyIDs...)))
+ assert.Equal(t, []keys.Signer{bSigner}, store.SignersForKeyIDs(bKeyIDs))
+ })
+ }
+}
diff --git a/pkg/deprecated/deprecated_repo_test.go b/pkg/deprecated/deprecated_repo_test.go
new file mode 100644
index 0000000..e65cb9e
--- /dev/null
+++ b/pkg/deprecated/deprecated_repo_test.go
@@ -0,0 +1,107 @@
+package deprecated
+
+import (
+ "crypto"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/json"
+ "testing"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+ repo "github.com/theupdateframework/go-tuf"
+ "github.com/theupdateframework/go-tuf/data"
+ _ "github.com/theupdateframework/go-tuf/pkg/deprecated/set_ecdsa"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type RepoSuite struct{}
+
+var _ = Suite(&RepoSuite{})
+
+func genKey(c *C, r *repo.Repo, role string) []string {
+ keyids, err := r.GenKey(role)
+ c.Assert(err, IsNil)
+ c.Assert(len(keyids) > 0, Equals, true)
+ return keyids
+}
+
+// Deprecated ecdsa key support: Support verification against roots that were
+// signed with hex-encoded ecdsa keys.
+func (rs *RepoSuite) TestDeprecatedHexEncodedKeysSucceed(c *C) {
+ type deprecatedP256Verifier struct {
+ PublicKey data.HexBytes `json:"public"`
+ }
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := repo.MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := repo.NewRepo(local)
+ c.Assert(err, IsNil)
+
+ r.Init(false)
+
+ // Add a root key with hex-encoded ecdsa format - compliant "ecdsa"
+ signer, err := keys.GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ pub := signer.PublicKey
+ keyValBytes, err := json.Marshal(&deprecatedP256Verifier{PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)})
+ c.Assert(err, IsNil)
+ publicData := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+ err = r.AddVerificationKey("root", publicData)
+ c.Assert(err, IsNil)
+
+ // Add a root key with hex-encoded ecdsa format - deprecated "ecdsa-sha2-nistp256"
+ signerDeprecated, err := keys.GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ pubDeprecated := signerDeprecated.PublicKey
+ keyValBytesDeprecated, err := json.Marshal(&deprecatedP256Verifier{PublicKey: elliptic.Marshal(pubDeprecated.Curve, pubDeprecated.X, pubDeprecated.Y)})
+ c.Assert(err, IsNil)
+ publicDataDeprecated := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256_OLD_FMT,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytesDeprecated,
+ }
+ err = r.AddVerificationKey("root", publicDataDeprecated)
+ c.Assert(err, IsNil)
+
+ // Add other keys as normal
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+
+ // Sign the root role manually
+ rootMeta, err := r.SignedMeta("root.json")
+ c.Assert(err, IsNil)
+ rootCanonical, err := cjson.EncodeCanonical(rootMeta.Signed)
+ c.Assert(err, IsNil)
+ hash := sha256.Sum256(rootCanonical)
+ rootSig, err := signer.PrivateKey.Sign(rand.Reader, hash[:], crypto.SHA256)
+ c.Assert(err, IsNil)
+ for _, id := range publicData.IDs() {
+ c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
+ KeyID: id,
+ Signature: rootSig}), IsNil)
+ }
+
+ rootSigDeprecated, err := signerDeprecated.PrivateKey.Sign(rand.Reader, hash[:], crypto.SHA256)
+ c.Assert(err, IsNil)
+ for _, id := range publicDataDeprecated.IDs() {
+ c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
+ KeyID: id,
+ Signature: rootSigDeprecated}), IsNil)
+ }
+
+ // Committing should succeed because the deprecated key pkg is added.
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+}
diff --git a/pkg/deprecated/set_ecdsa/set_ecdsa.go b/pkg/deprecated/set_ecdsa/set_ecdsa.go
new file mode 100644
index 0000000..13967e2
--- /dev/null
+++ b/pkg/deprecated/set_ecdsa/set_ecdsa.go
@@ -0,0 +1,26 @@
+package set_ecdsa
+
+import (
+ "errors"
+
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+/*
+ Importing this package will allow support for both hex-encoded ECDSA
+ verifiers and PEM-encoded ECDSA verifiers.
+ Note that this package imports "github.com/theupdateframework/go-tuf/pkg/keys"
+ and overrides the ECDSA verifier loaded at init time in that package.
+*/
+
+func init() {
+ _, ok := keys.VerifierMap.Load(data.KeyTypeECDSA_SHA2_P256)
+ if !ok {
+ panic(errors.New("expected to override previously loaded PEM-only ECDSA verifier"))
+ }
+ // store a mapping for both data.KeyTypeECDSA_SHA2_P256_OLD_FMT and data.KeyTypeECDSA_SHA2_P256
+ // in case a client is verifying using both the old non-compliant format and a newly generated root
+ keys.VerifierMap.Store(data.KeyTypeECDSA_SHA2_P256, keys.NewDeprecatedEcdsaVerifier) // compliant format
+ keys.VerifierMap.Store(data.KeyTypeECDSA_SHA2_P256_OLD_FMT, keys.NewDeprecatedEcdsaVerifier) // deprecated format
+}
diff --git a/pkg/keys/deprecated_ecdsa.go b/pkg/keys/deprecated_ecdsa.go
new file mode 100644
index 0000000..6d48c9d
--- /dev/null
+++ b/pkg/keys/deprecated_ecdsa.go
@@ -0,0 +1,101 @@
+package keys
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/sha256"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+func NewDeprecatedEcdsaVerifier() Verifier {
+ return &ecdsaVerifierWithDeprecatedSupport{}
+}
+
+type ecdsaVerifierWithDeprecatedSupport struct {
+ key *data.PublicKey
+ // This will switch based on whether this is a PEM-encoded key
+ // or a deprecated hex-encoded key.
+ Verifier
+}
+
+func (p *ecdsaVerifierWithDeprecatedSupport) UnmarshalPublicKey(key *data.PublicKey) error {
+ p.key = key
+ pemVerifier := &EcdsaVerifier{}
+ if err := pemVerifier.UnmarshalPublicKey(key); err != nil {
+ // Try the deprecated hex-encoded verifier
+ hexVerifier := &deprecatedP256Verifier{}
+ if err := hexVerifier.UnmarshalPublicKey(key); err != nil {
+ return err
+ }
+ p.Verifier = hexVerifier
+ return nil
+ }
+ p.Verifier = pemVerifier
+ return nil
+}
+
+/*
+ Deprecated ecdsaVerifier that used hex-encoded public keys.
+ This MAY be used to verify existing metadata that used this
+ old format. This will be deprecated soon, ensure that repositories
+ are re-signed and clients receieve a fully compliant root.
+*/
+
+type deprecatedP256Verifier struct {
+ PublicKey data.HexBytes `json:"public"`
+ key *data.PublicKey
+}
+
+func (p *deprecatedP256Verifier) Public() string {
+ return p.PublicKey.String()
+}
+
+func (p *deprecatedP256Verifier) Verify(msg, sigBytes []byte) error {
+ x, y := elliptic.Unmarshal(elliptic.P256(), p.PublicKey)
+ k := &ecdsa.PublicKey{
+ Curve: elliptic.P256(),
+ X: x,
+ Y: y,
+ }
+
+ hash := sha256.Sum256(msg)
+
+ if !ecdsa.VerifyASN1(k, hash[:], sigBytes) {
+ return errors.New("tuf: deprecated ecdsa signature verification failed")
+ }
+ return nil
+}
+
+func (p *deprecatedP256Verifier) MarshalPublicKey() *data.PublicKey {
+ return p.key
+}
+
+func (p *deprecatedP256Verifier) UnmarshalPublicKey(key *data.PublicKey) error {
+ // Prepare decoder limited to 512Kb
+ dec := json.NewDecoder(io.LimitReader(bytes.NewReader(key.Value), MaxJSONKeySize))
+
+ // Unmarshal key value
+ if err := dec.Decode(p); err != nil {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+ return fmt.Errorf("tuf: the public key is truncated or too large: %w", err)
+ }
+ return err
+ }
+
+ curve := elliptic.P256()
+
+ // Parse as uncompressed marshalled point.
+ x, _ := elliptic.Unmarshal(curve, p.PublicKey)
+ if x == nil {
+ return errors.New("tuf: invalid ecdsa public key point")
+ }
+
+ p.key = key
+ return nil
+}
diff --git a/pkg/keys/deprecated_ecdsa_test.go b/pkg/keys/deprecated_ecdsa_test.go
new file mode 100644
index 0000000..ddfaa84
--- /dev/null
+++ b/pkg/keys/deprecated_ecdsa_test.go
@@ -0,0 +1,129 @@
+package keys
+
+import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/json"
+ "errors"
+
+ "github.com/theupdateframework/go-tuf/data"
+ . "gopkg.in/check.v1"
+)
+
+type DeprecatedECDSASuite struct{}
+
+var _ = Suite(DeprecatedECDSASuite{})
+
+type deprecatedEcdsaSigner struct {
+ *ecdsa.PrivateKey
+}
+
+type deprecatedEcdsaPublic struct {
+ PublicKey data.HexBytes `json:"public"`
+}
+
+func (s deprecatedEcdsaSigner) PublicData() *data.PublicKey {
+ pub := s.Public().(*ecdsa.PublicKey)
+ keyValBytes, _ := json.Marshal(deprecatedEcdsaPublic{
+ PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)})
+ return &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+}
+
+func (s deprecatedEcdsaSigner) SignMessage(message []byte) ([]byte, error) {
+ hash := sha256.Sum256(message)
+ return s.PrivateKey.Sign(rand.Reader, hash[:], crypto.SHA256)
+}
+
+func (s deprecatedEcdsaSigner) ContainsID(id string) bool {
+ return s.PublicData().ContainsID(id)
+}
+
+func (deprecatedEcdsaSigner) MarshalPrivateKey() (*data.PrivateKey, error) {
+ return nil, errors.New("not implemented for test")
+}
+
+func (deprecatedEcdsaSigner) UnmarshalPrivateKey(key *data.PrivateKey) error {
+ return errors.New("not implemented for test")
+}
+
+func generatedDeprecatedSigner() (*deprecatedEcdsaSigner, error) {
+ privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+ return &deprecatedEcdsaSigner{privkey}, nil
+}
+
+func (DeprecatedECDSASuite) TestSignVerifyDeprecatedFormat(c *C) {
+ // Create an ecdsa key with a deprecated format.
+ signer, err := generatedDeprecatedSigner()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+
+ pub := signer.PublicKey
+
+ keyValBytes, err := json.Marshal(&deprecatedP256Verifier{PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)})
+ c.Assert(err, IsNil)
+ publicData := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+
+ deprecatedEcdsa := NewDeprecatedEcdsaVerifier()
+ err = deprecatedEcdsa.UnmarshalPublicKey(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(deprecatedEcdsa.Verify(msg, sig), IsNil)
+}
+
+func (DeprecatedECDSASuite) TestECDSAVerifyMismatchMessage(c *C) {
+ signer, err := generatedDeprecatedSigner()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+ publicData := signer.PublicData()
+ deprecatedEcdsa := NewDeprecatedEcdsaVerifier()
+ err = deprecatedEcdsa.UnmarshalPublicKey(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(deprecatedEcdsa.Verify([]byte("notfoo"), sig), ErrorMatches, "tuf: deprecated ecdsa signature verification failed")
+}
+
+func (DeprecatedECDSASuite) TestECDSAVerifyMismatchPubKey(c *C) {
+ signer, err := generatedDeprecatedSigner()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+
+ signerNew, err := generatedDeprecatedSigner()
+ c.Assert(err, IsNil)
+ deprecatedEcdsa := NewDeprecatedEcdsaVerifier()
+ err = deprecatedEcdsa.UnmarshalPublicKey(signerNew.PublicData())
+ c.Assert(err, IsNil)
+ c.Assert(deprecatedEcdsa.Verify([]byte("notfoo"), sig), ErrorMatches, "tuf: deprecated ecdsa signature verification failed")
+}
+
+func (DeprecatedECDSASuite) TestMarshalUnmarshalPublicKey(c *C) {
+ signer, err := generatedDeprecatedSigner()
+ c.Assert(err, IsNil)
+
+ pub := signer.PublicData()
+
+ deprecatedEcdsa := NewDeprecatedEcdsaVerifier()
+ err = deprecatedEcdsa.UnmarshalPublicKey(pub)
+ c.Assert(err, IsNil)
+
+ c.Assert(deprecatedEcdsa.MarshalPublicKey(), DeepEquals, pub)
+}
diff --git a/pkg/keys/ecdsa.go b/pkg/keys/ecdsa.go
new file mode 100644
index 0000000..9740d1f
--- /dev/null
+++ b/pkg/keys/ecdsa.go
@@ -0,0 +1,173 @@
+package keys
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+func init() {
+ // Note: we use LoadOrStore here to prevent accidentally overriding the
+ // an explicit deprecated ECDSA verifier.
+ // TODO: When deprecated ECDSA is removed, this can switch back to Store.
+ VerifierMap.LoadOrStore(data.KeyTypeECDSA_SHA2_P256_OLD_FMT, NewEcdsaVerifier)
+ VerifierMap.LoadOrStore(data.KeyTypeECDSA_SHA2_P256, NewEcdsaVerifier)
+ SignerMap.Store(data.KeyTypeECDSA_SHA2_P256_OLD_FMT, newEcdsaSigner)
+ SignerMap.Store(data.KeyTypeECDSA_SHA2_P256, newEcdsaSigner)
+}
+
+func NewEcdsaVerifier() Verifier {
+ return &EcdsaVerifier{}
+}
+
+func newEcdsaSigner() Signer {
+ return &ecdsaSigner{}
+}
+
+type EcdsaVerifier struct {
+ PublicKey *PKIXPublicKey `json:"public"`
+ ecdsaKey *ecdsa.PublicKey
+ key *data.PublicKey
+}
+
+func (p *EcdsaVerifier) Public() string {
+ // This is already verified to succeed when unmarshalling a public key.
+ r, err := x509.MarshalPKIXPublicKey(p.ecdsaKey)
+ if err != nil {
+ // TODO: Gracefully handle these errors.
+ // See https://github.com/theupdateframework/go-tuf/issues/363
+ panic(err)
+ }
+ return string(r)
+}
+
+func (p *EcdsaVerifier) Verify(msg, sigBytes []byte) error {
+ hash := sha256.Sum256(msg)
+
+ if !ecdsa.VerifyASN1(p.ecdsaKey, hash[:], sigBytes) {
+ return errors.New("tuf: ecdsa signature verification failed")
+ }
+ return nil
+}
+
+func (p *EcdsaVerifier) MarshalPublicKey() *data.PublicKey {
+ return p.key
+}
+
+func (p *EcdsaVerifier) UnmarshalPublicKey(key *data.PublicKey) error {
+ // Prepare decoder limited to 512Kb
+ dec := json.NewDecoder(io.LimitReader(bytes.NewReader(key.Value), MaxJSONKeySize))
+
+ // Unmarshal key value
+ if err := dec.Decode(p); err != nil {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+ return fmt.Errorf("tuf: the public key is truncated or too large: %w", err)
+ }
+ return err
+ }
+
+ ecdsaKey, ok := p.PublicKey.PublicKey.(*ecdsa.PublicKey)
+ if !ok {
+ return fmt.Errorf("invalid public key")
+ }
+
+ if _, err := x509.MarshalPKIXPublicKey(ecdsaKey); err != nil {
+ return fmt.Errorf("marshalling to PKIX key: invalid public key")
+ }
+
+ p.ecdsaKey = ecdsaKey
+ p.key = key
+ return nil
+}
+
+type ecdsaSigner struct {
+ *ecdsa.PrivateKey
+}
+
+type ecdsaPrivateKeyValue struct {
+ Private string `json:"private"`
+ Public *PKIXPublicKey `json:"public"`
+}
+
+func (s *ecdsaSigner) PublicData() *data.PublicKey {
+ // This uses a trusted public key JSON format with a trusted Public value.
+ keyValBytes, _ := json.Marshal(EcdsaVerifier{PublicKey: &PKIXPublicKey{PublicKey: s.Public()}})
+ return &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+}
+
+func (s *ecdsaSigner) SignMessage(message []byte) ([]byte, error) {
+ hash := sha256.Sum256(message)
+ return ecdsa.SignASN1(rand.Reader, s.PrivateKey, hash[:])
+}
+
+func (s *ecdsaSigner) MarshalPrivateKey() (*data.PrivateKey, error) {
+ priv, err := x509.MarshalECPrivateKey(s.PrivateKey)
+ if err != nil {
+ return nil, err
+ }
+ pemKey := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: priv})
+ val, err := json.Marshal(ecdsaPrivateKeyValue{
+ Private: string(pemKey),
+ Public: &PKIXPublicKey{PublicKey: s.Public()},
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &data.PrivateKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: val,
+ }, nil
+}
+
+func (s *ecdsaSigner) UnmarshalPrivateKey(key *data.PrivateKey) error {
+ val := ecdsaPrivateKeyValue{}
+ if err := json.Unmarshal(key.Value, &val); err != nil {
+ return err
+ }
+ block, _ := pem.Decode([]byte(val.Private))
+ if block == nil {
+ return errors.New("invalid PEM value")
+ }
+ if block.Type != "EC PRIVATE KEY" {
+ return fmt.Errorf("invalid block type: %s", block.Type)
+ }
+ k, err := x509.ParseECPrivateKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+ if k.Curve != elliptic.P256() {
+ return errors.New("unsupported ecdsa curve")
+ }
+ if _, err := json.Marshal(EcdsaVerifier{
+ PublicKey: &PKIXPublicKey{PublicKey: k.Public()}}); err != nil {
+ return fmt.Errorf("invalid public key: %s", err)
+ }
+
+ s.PrivateKey = k
+ return nil
+}
+
+func GenerateEcdsaKey() (*ecdsaSigner, error) {
+ privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+ return &ecdsaSigner{privkey}, nil
+}
diff --git a/pkg/keys/ecdsa_test.go b/pkg/keys/ecdsa_test.go
new file mode 100644
index 0000000..2fe6348
--- /dev/null
+++ b/pkg/keys/ecdsa_test.go
@@ -0,0 +1,163 @@
+package keys
+
+import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "io"
+ "strings"
+
+ fuzz "github.com/google/gofuzz"
+ "github.com/theupdateframework/go-tuf/data"
+ . "gopkg.in/check.v1"
+)
+
+type ECDSASuite struct{}
+
+var _ = Suite(ECDSASuite{})
+
+func (ECDSASuite) TestSignVerify(c *C) {
+ signer, err := GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+ publicData := signer.PublicData()
+ pubKey, err := GetVerifier(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.Verify(msg, sig), IsNil)
+}
+
+func (ECDSASuite) TestECDSAVerifyMismatchMessage(c *C) {
+ signer, err := GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+ publicData := signer.PublicData()
+ pubKey, err := GetVerifier(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.Verify([]byte("notfoo"), sig), ErrorMatches, "tuf: ecdsa signature verification failed")
+}
+
+func (ECDSASuite) TestECDSAVerifyMismatchPubKey(c *C) {
+ signer, err := GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+
+ signerNew, err := GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ pubKey, err := GetVerifier(signerNew.PublicData())
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.Verify([]byte("notfoo"), sig), ErrorMatches, "tuf: ecdsa signature verification failed")
+}
+
+func (ECDSASuite) TestSignVerifyDeprecatedFails(c *C) {
+ // Create an ecdsa key with a deprecated format.
+ signer, err := GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+
+ type deprecatedP256Verifier struct {
+ PublicKey data.HexBytes `json:"public"`
+ }
+ pub := signer.PublicKey
+ keyValBytes, err := json.Marshal(&deprecatedP256Verifier{PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)})
+ c.Assert(err, IsNil)
+ publicData := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+
+ _, err = GetVerifier(publicData)
+ c.Assert(err, ErrorMatches, "tuf: error unmarshalling key: invalid PEM value")
+}
+
+func (ECDSASuite) TestMarshalUnmarshalPublicKey(c *C) {
+ signer, err := GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ publicData := signer.PublicData()
+ pubKey, err := GetVerifier(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.MarshalPublicKey(), DeepEquals, publicData)
+}
+
+func (ECDSASuite) TestMarshalUnmarshalPrivateKey(c *C) {
+ signer, err := GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ privateData, err := signer.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ c.Assert(privateData.Type, Equals, data.KeyTypeECDSA_SHA2_P256)
+ c.Assert(privateData.Scheme, Equals, data.KeySchemeECDSA_SHA2_P256)
+ c.Assert(privateData.Algorithms, DeepEquals, data.HashAlgorithms)
+ s, err := GetSigner(privateData)
+ c.Assert(err, IsNil)
+ c.Assert(s, DeepEquals, signer)
+}
+
+func (ECDSASuite) TestUnmarshalECDSA(c *C) {
+ priv, err := ecdsa.GenerateKey(elliptic.P256(), strings.NewReader("00001-deterministic-buffer-for-key-generation"))
+ c.Assert(err, IsNil)
+
+ signer := &ecdsaSigner{priv}
+ goodKey := signer.PublicData()
+
+ verifier := NewEcdsaVerifier()
+ c.Assert(verifier.UnmarshalPublicKey(goodKey), IsNil)
+}
+
+func (ECDSASuite) TestUnmarshalECDSA_Invalid(c *C) {
+ badKeyValue, err := json.Marshal(true)
+ c.Assert(err, IsNil)
+
+ badKey := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: badKeyValue,
+ }
+ verifier := NewEcdsaVerifier()
+ c.Assert(verifier.UnmarshalPublicKey(badKey), ErrorMatches, "json: cannot unmarshal.*")
+}
+
+func (ECDSASuite) TestUnmarshalECDSA_FastFuzz(c *C) {
+ verifier := NewEcdsaVerifier()
+ for i := 0; i < 50; i++ {
+ // Ensure no basic panic
+
+ f := fuzz.New()
+ var publicData data.PublicKey
+ f.Fuzz(&publicData)
+
+ verifier.UnmarshalPublicKey(&publicData)
+ }
+}
+
+func (ECDSASuite) TestUnmarshalECDSA_TooLongContent(c *C) {
+ randomSeed := make([]byte, MaxJSONKeySize)
+ _, err := io.ReadFull(rand.Reader, randomSeed)
+ c.Assert(err, IsNil)
+
+ tooLongPayload, err := json.Marshal(
+ &ed25519Verifier{
+ PublicKey: data.HexBytes(hex.EncodeToString(randomSeed)),
+ },
+ )
+ c.Assert(err, IsNil)
+
+ badKey := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: tooLongPayload,
+ }
+ verifier := NewEcdsaVerifier()
+ err = verifier.UnmarshalPublicKey(badKey)
+ c.Assert(errors.Is(err, io.ErrUnexpectedEOF), Equals, true)
+}
diff --git a/pkg/keys/ed25519.go b/pkg/keys/ed25519.go
new file mode 100644
index 0000000..1e4c66c
--- /dev/null
+++ b/pkg/keys/ed25519.go
@@ -0,0 +1,161 @@
+package keys
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/ed25519"
+ "crypto/rand"
+ "crypto/subtle"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+func init() {
+ SignerMap.Store(data.KeyTypeEd25519, NewEd25519Signer)
+ VerifierMap.Store(data.KeyTypeEd25519, NewEd25519Verifier)
+}
+
+func NewEd25519Signer() Signer {
+ return &ed25519Signer{}
+}
+
+func NewEd25519Verifier() Verifier {
+ return &ed25519Verifier{}
+}
+
+type ed25519Verifier struct {
+ PublicKey data.HexBytes `json:"public"`
+ key *data.PublicKey
+}
+
+func (e *ed25519Verifier) Public() string {
+ return string(e.PublicKey)
+}
+
+func (e *ed25519Verifier) Verify(msg, sig []byte) error {
+ if !ed25519.Verify([]byte(e.PublicKey), msg, sig) {
+ return errors.New("tuf: ed25519 signature verification failed")
+ }
+ return nil
+}
+
+func (e *ed25519Verifier) MarshalPublicKey() *data.PublicKey {
+ return e.key
+}
+
+func (e *ed25519Verifier) UnmarshalPublicKey(key *data.PublicKey) error {
+ e.key = key
+
+ // Prepare decoder limited to 512Kb
+ dec := json.NewDecoder(io.LimitReader(bytes.NewReader(key.Value), MaxJSONKeySize))
+
+ // Unmarshal key value
+ if err := dec.Decode(e); err != nil {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+ return fmt.Errorf("tuf: the public key is truncated or too large: %w", err)
+ }
+ return err
+ }
+ if n := len(e.PublicKey); n != ed25519.PublicKeySize {
+ return fmt.Errorf("tuf: unexpected public key length for ed25519 key, expected %d, got %d", ed25519.PublicKeySize, n)
+ }
+ return nil
+}
+
+type Ed25519PrivateKeyValue struct {
+ Public data.HexBytes `json:"public"`
+ Private data.HexBytes `json:"private"`
+}
+
+type ed25519Signer struct {
+ ed25519.PrivateKey
+}
+
+func GenerateEd25519Key() (*ed25519Signer, error) {
+ _, private, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &ed25519Signer{
+ PrivateKey: ed25519.PrivateKey(data.HexBytes(private)),
+ }, nil
+}
+
+func NewEd25519SignerFromKey(keyValue Ed25519PrivateKeyValue) *ed25519Signer {
+ return &ed25519Signer{
+ PrivateKey: ed25519.PrivateKey(data.HexBytes(keyValue.Private)),
+ }
+}
+
+func (e *ed25519Signer) SignMessage(message []byte) ([]byte, error) {
+ return e.Sign(rand.Reader, message, crypto.Hash(0))
+}
+
+func (e *ed25519Signer) MarshalPrivateKey() (*data.PrivateKey, error) {
+ valueBytes, err := json.Marshal(Ed25519PrivateKeyValue{
+ Public: data.HexBytes([]byte(e.PrivateKey.Public().(ed25519.PublicKey))),
+ Private: data.HexBytes(e.PrivateKey),
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &data.PrivateKey{
+ Type: data.KeyTypeEd25519,
+ Scheme: data.KeySchemeEd25519,
+ Algorithms: data.HashAlgorithms,
+ Value: valueBytes,
+ }, nil
+}
+
+func (e *ed25519Signer) UnmarshalPrivateKey(key *data.PrivateKey) error {
+ keyValue := &Ed25519PrivateKeyValue{}
+
+ // Prepare decoder limited to 512Kb
+ dec := json.NewDecoder(io.LimitReader(bytes.NewReader(key.Value), MaxJSONKeySize))
+
+ // Unmarshal key value
+ if err := dec.Decode(keyValue); err != nil {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+ return fmt.Errorf("tuf: the private key is truncated or too large: %w", err)
+ }
+ }
+
+ // Check private key length
+ if n := len(keyValue.Private); n != ed25519.PrivateKeySize {
+ return fmt.Errorf("tuf: invalid ed25519 private key length, expected %d, got %d", ed25519.PrivateKeySize, n)
+ }
+
+ // Generate public key from private key
+ pub, _, err := ed25519.GenerateKey(bytes.NewReader(keyValue.Private))
+ if err != nil {
+ return fmt.Errorf("tuf: unable to derive public key from private key: %w", err)
+ }
+
+ // Compare keys
+ if subtle.ConstantTimeCompare(keyValue.Public, pub) != 1 {
+ return errors.New("tuf: public and private keys don't match")
+ }
+
+ // Prepare signer
+ *e = ed25519Signer{
+ PrivateKey: ed25519.PrivateKey(data.HexBytes(keyValue.Private)),
+ }
+ return nil
+}
+
+func (e *ed25519Signer) PublicData() *data.PublicKey {
+ keyValBytes, _ := json.Marshal(ed25519Verifier{PublicKey: []byte(e.PrivateKey.Public().(ed25519.PublicKey))})
+ return &data.PublicKey{
+ Type: data.KeyTypeEd25519,
+ Scheme: data.KeySchemeEd25519,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+}
diff --git a/pkg/keys/ed25519_test.go b/pkg/keys/ed25519_test.go
new file mode 100644
index 0000000..a17cc14
--- /dev/null
+++ b/pkg/keys/ed25519_test.go
@@ -0,0 +1,99 @@
+package keys
+
+import (
+ "crypto/ed25519"
+ "crypto/rand"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "io"
+ "strings"
+
+ fuzz "github.com/google/gofuzz"
+ "github.com/theupdateframework/go-tuf/data"
+ . "gopkg.in/check.v1"
+)
+
+type Ed25519Suite struct{}
+
+var _ = Suite(&Ed25519Suite{})
+
+func (Ed25519Suite) TestUnmarshalEd25519(c *C) {
+ pub, _, err := ed25519.GenerateKey(strings.NewReader("00001-deterministic-buffer-for-key-generation"))
+ c.Assert(err, IsNil)
+
+ publicKey, err := json.Marshal(map[string]string{
+ "public": hex.EncodeToString(pub),
+ })
+ c.Assert(err, IsNil)
+
+ badKey := &data.PublicKey{
+ Type: data.KeyTypeEd25519,
+ Scheme: data.KeySchemeEd25519,
+ Algorithms: data.HashAlgorithms,
+ Value: publicKey,
+ }
+ verifier := NewEd25519Verifier()
+ c.Assert(verifier.UnmarshalPublicKey(badKey), IsNil)
+}
+
+func (Ed25519Suite) TestUnmarshalEd25519_Invalid(c *C) {
+ badKeyValue, err := json.Marshal(true)
+ c.Assert(err, IsNil)
+ badKey := &data.PublicKey{
+ Type: data.KeyTypeEd25519,
+ Scheme: data.KeySchemeEd25519,
+ Algorithms: data.HashAlgorithms,
+ Value: badKeyValue,
+ }
+ verifier := NewEd25519Verifier()
+ c.Assert(verifier.UnmarshalPublicKey(badKey), ErrorMatches, "json: cannot unmarshal.*")
+}
+
+func (Ed25519Suite) TestUnmarshalEd25519_FastFuzz(c *C) {
+ verifier := NewEd25519Verifier()
+ for i := 0; i < 50; i++ {
+ // Ensure no basic panic
+
+ f := fuzz.New()
+ var publicData data.PublicKey
+ f.Fuzz(&publicData)
+
+ verifier.UnmarshalPublicKey(&publicData)
+ }
+}
+
+func (Ed25519Suite) TestUnmarshalEd25519_TooLongContent(c *C) {
+ randomSeed := make([]byte, MaxJSONKeySize)
+ _, err := io.ReadFull(rand.Reader, randomSeed)
+ c.Assert(err, IsNil)
+
+ tooLongPayload, err := json.Marshal(
+ &ed25519Verifier{
+ PublicKey: data.HexBytes(hex.EncodeToString(randomSeed)),
+ },
+ )
+ c.Assert(err, IsNil)
+
+ badKey := &data.PublicKey{
+ Type: data.KeyTypeEd25519,
+ Scheme: data.KeySchemeEd25519,
+ Algorithms: data.HashAlgorithms,
+ Value: tooLongPayload,
+ }
+ verifier := NewEd25519Verifier()
+ err = verifier.UnmarshalPublicKey(badKey)
+ c.Assert(errors.Is(err, io.ErrUnexpectedEOF), Equals, true)
+}
+
+func (Ed25519Suite) TestSignVerify(c *C) {
+ signer, err := GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+ publicData := signer.PublicData()
+ pubKey, err := GetVerifier(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.Verify(msg, sig), IsNil)
+}
diff --git a/pkg/keys/keys.go b/pkg/keys/keys.go
new file mode 100644
index 0000000..dc5f3ea
--- /dev/null
+++ b/pkg/keys/keys.go
@@ -0,0 +1,82 @@
+package keys
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+// MaxJSONKeySize defines the maximum length of a JSON payload.
+const MaxJSONKeySize = 512 * 1024 // 512Kb
+
+// SignerMap stores mapping between key type strings and signer constructors.
+var SignerMap sync.Map
+
+// Verifier stores mapping between key type strings and verifier constructors.
+var VerifierMap sync.Map
+
+var (
+ ErrInvalid = errors.New("tuf: signature verification failed")
+ ErrInvalidKey = errors.New("invalid key")
+)
+
+// A Verifier verifies public key signatures.
+type Verifier interface {
+ // UnmarshalPublicKey takes key data to a working verifier implementation for the key type.
+ // This performs any validation over the data.PublicKey to ensure that the verifier is usable
+ // to verify signatures.
+ UnmarshalPublicKey(key *data.PublicKey) error
+
+ // MarshalPublicKey returns the data.PublicKey object associated with the verifier.
+ MarshalPublicKey() *data.PublicKey
+
+ // This is the public string used as a unique identifier for the verifier instance.
+ Public() string
+
+ // Verify takes a message and signature, all as byte slices,
+ // and determines whether the signature is valid for the given
+ // key and message.
+ Verify(msg, sig []byte) error
+}
+
+type Signer interface {
+ // MarshalPrivateKey returns the private key data.
+ MarshalPrivateKey() (*data.PrivateKey, error)
+
+ // UnmarshalPrivateKey takes private key data to a working Signer implementation for the key type.
+ UnmarshalPrivateKey(key *data.PrivateKey) error
+
+ // Returns the public data.PublicKey from the private key
+ PublicData() *data.PublicKey
+
+ // Sign returns the signature of the message.
+ // The signer is expected to do its own hashing, so the full message will be
+ // provided as the message to Sign with a zero opts.HashFunc().
+ SignMessage(message []byte) ([]byte, error)
+}
+
+func GetVerifier(key *data.PublicKey) (Verifier, error) {
+ st, ok := VerifierMap.Load(key.Type)
+ if !ok {
+ return nil, ErrInvalidKey
+ }
+ s := st.(func() Verifier)()
+ if err := s.UnmarshalPublicKey(key); err != nil {
+ return nil, fmt.Errorf("tuf: error unmarshalling key: %w", err)
+ }
+ return s, nil
+}
+
+func GetSigner(key *data.PrivateKey) (Signer, error) {
+ st, ok := SignerMap.Load(key.Type)
+ if !ok {
+ return nil, ErrInvalidKey
+ }
+ s := st.(func() Signer)()
+ if err := s.UnmarshalPrivateKey(key); err != nil {
+ return nil, fmt.Errorf("tuf: error unmarshalling key: %w", err)
+ }
+ return s, nil
+}
diff --git a/pkg/keys/keys_test.go b/pkg/keys/keys_test.go
new file mode 100644
index 0000000..c1a7d01
--- /dev/null
+++ b/pkg/keys/keys_test.go
@@ -0,0 +1,39 @@
+package keys
+
+import (
+ "testing"
+
+ "github.com/theupdateframework/go-tuf/data"
+ . "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type KeysSuite struct{}
+
+var _ = Suite(&KeysSuite{})
+
+func (KeysSuite) TestSignerKeyIDs(c *C) {
+ _, err := GenerateEd25519Key()
+ c.Assert(err, IsNil)
+
+ // If we have a TUF-0.9 key, we won't have a scheme.
+ signer, err := GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ privKey, err := signer.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ privKey.Scheme = ""
+ err = signer.UnmarshalPrivateKey(privKey)
+ c.Assert(err, IsNil)
+
+ // Make sure we preserve ids if we don't have any
+ // keyid_hash_algorithms.
+ signer, err = GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ privKey, err = signer.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ privKey.Algorithms = []data.HashAlgorithm{}
+ err = signer.UnmarshalPrivateKey(privKey)
+ c.Assert(err, IsNil)
+}
diff --git a/pkg/keys/pkix.go b/pkg/keys/pkix.go
new file mode 100644
index 0000000..e58d4c9
--- /dev/null
+++ b/pkg/keys/pkix.go
@@ -0,0 +1,56 @@
+package keys
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io"
+)
+
+type PKIXPublicKey struct {
+ crypto.PublicKey
+}
+
+func (p *PKIXPublicKey) MarshalJSON() ([]byte, error) {
+ bytes, err := x509.MarshalPKIXPublicKey(p.PublicKey)
+ if err != nil {
+ return nil, err
+ }
+ pemBytes := pem.EncodeToMemory(&pem.Block{
+ Type: "PUBLIC KEY",
+ Bytes: bytes,
+ })
+ return json.Marshal(string(pemBytes))
+}
+
+func (p *PKIXPublicKey) UnmarshalJSON(b []byte) error {
+ var pemValue string
+ // Prepare decoder limited to 512Kb
+ dec := json.NewDecoder(io.LimitReader(bytes.NewReader(b), MaxJSONKeySize))
+
+ // Unmarshal key value
+ if err := dec.Decode(&pemValue); err != nil {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+ return fmt.Errorf("tuf: the public key is truncated or too large: %w", err)
+ }
+ return err
+ }
+
+ block, _ := pem.Decode([]byte(pemValue))
+ if block == nil {
+ return errors.New("invalid PEM value")
+ }
+ if block.Type != "PUBLIC KEY" {
+ return fmt.Errorf("invalid block type: %s", block.Type)
+ }
+ pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+ p.PublicKey = pub
+ return nil
+}
diff --git a/pkg/keys/pkix_test.go b/pkg/keys/pkix_test.go
new file mode 100644
index 0000000..4debdde
--- /dev/null
+++ b/pkg/keys/pkix_test.go
@@ -0,0 +1,62 @@
+package keys
+
+import (
+ "crypto/ecdsa"
+ "crypto/rand"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "io"
+
+ . "gopkg.in/check.v1"
+)
+
+const ecdsaKey = `-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEftgasQA68yvumeXZmcOTSIHKfbmx
+WT1oYuRF0Un3tKxnzip6xAYwlz0Dt96DUh+0P7BruHH2O6s4MiRR9/TuNw==
+-----END PUBLIC KEY-----
+`
+
+type PKIXSuite struct{}
+
+var _ = Suite(&PKIXSuite{})
+
+func (PKIXSuite) TestMarshalJSON(c *C) {
+ block, _ := pem.Decode([]byte(ecdsaKey))
+ key, err := x509.ParsePKIXPublicKey(block.Bytes)
+ c.Assert(err, IsNil)
+ k := PKIXPublicKey{PublicKey: key}
+ buf, err := json.Marshal(&k)
+ c.Assert(err, IsNil)
+ var val string
+ err = json.Unmarshal(buf, &val)
+ c.Assert(err, IsNil)
+ c.Assert(val, Equals, ecdsaKey)
+}
+
+func (PKIXSuite) TestUnmarshalJSON(c *C) {
+ buf, err := json.Marshal(ecdsaKey)
+ c.Assert(err, IsNil)
+ var k PKIXPublicKey
+ err = json.Unmarshal(buf, &k)
+ c.Assert(err, IsNil)
+ c.Assert(k.PublicKey, FitsTypeOf, (*ecdsa.PublicKey)(nil))
+}
+
+func (PKIXSuite) TestUnmarshalPKIX_TooLongContent(c *C) {
+ randomSeed := make([]byte, MaxJSONKeySize)
+ _, err := io.ReadFull(rand.Reader, randomSeed)
+ c.Assert(err, IsNil)
+
+ pemBytes := pem.EncodeToMemory(&pem.Block{
+ Type: "PUBLIC KEY",
+ Bytes: randomSeed,
+ })
+ tooLongPayload, err := json.Marshal(string(pemBytes))
+ c.Assert(err, IsNil)
+
+ var k PKIXPublicKey
+ err = json.Unmarshal(tooLongPayload, &k)
+ c.Assert(errors.Is(err, io.ErrUnexpectedEOF), Equals, true)
+}
diff --git a/pkg/keys/rsa.go b/pkg/keys/rsa.go
new file mode 100644
index 0000000..618f104
--- /dev/null
+++ b/pkg/keys/rsa.go
@@ -0,0 +1,162 @@
+package keys
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+func init() {
+ VerifierMap.Store(data.KeyTypeRSASSA_PSS_SHA256, newRsaVerifier)
+ SignerMap.Store(data.KeyTypeRSASSA_PSS_SHA256, newRsaSigner)
+}
+
+func newRsaVerifier() Verifier {
+ return &rsaVerifier{}
+}
+
+func newRsaSigner() Signer {
+ return &rsaSigner{}
+}
+
+type rsaVerifier struct {
+ PublicKey *PKIXPublicKey `json:"public"`
+ rsaKey *rsa.PublicKey
+ key *data.PublicKey
+}
+
+func (p *rsaVerifier) Public() string {
+ // This is already verified to succeed when unmarshalling a public key.
+ r, err := x509.MarshalPKIXPublicKey(p.rsaKey)
+ if err != nil {
+ // TODO: Gracefully handle these errors.
+ // See https://github.com/theupdateframework/go-tuf/issues/363
+ panic(err)
+ }
+ return string(r)
+}
+
+func (p *rsaVerifier) Verify(msg, sigBytes []byte) error {
+ hash := sha256.Sum256(msg)
+
+ return rsa.VerifyPSS(p.rsaKey, crypto.SHA256, hash[:], sigBytes, &rsa.PSSOptions{})
+}
+
+func (p *rsaVerifier) MarshalPublicKey() *data.PublicKey {
+ return p.key
+}
+
+func (p *rsaVerifier) UnmarshalPublicKey(key *data.PublicKey) error {
+ // Prepare decoder limited to 512Kb
+ dec := json.NewDecoder(io.LimitReader(bytes.NewReader(key.Value), MaxJSONKeySize))
+
+ // Unmarshal key value
+ if err := dec.Decode(p); err != nil {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+ return fmt.Errorf("tuf: the public key is truncated or too large: %w", err)
+ }
+ return err
+ }
+
+ rsaKey, ok := p.PublicKey.PublicKey.(*rsa.PublicKey)
+ if !ok {
+ return fmt.Errorf("invalid public key")
+ }
+
+ if _, err := x509.MarshalPKIXPublicKey(rsaKey); err != nil {
+ return fmt.Errorf("marshalling to PKIX key: invalid public key")
+ }
+
+ p.rsaKey = rsaKey
+ p.key = key
+ return nil
+}
+
+type rsaSigner struct {
+ *rsa.PrivateKey
+}
+
+type rsaPrivateKeyValue struct {
+ Private string `json:"private"`
+ Public *PKIXPublicKey `json:"public"`
+}
+
+func (s *rsaSigner) PublicData() *data.PublicKey {
+ keyValBytes, _ := json.Marshal(rsaVerifier{PublicKey: &PKIXPublicKey{PublicKey: s.Public()}})
+ return &data.PublicKey{
+ Type: data.KeyTypeRSASSA_PSS_SHA256,
+ Scheme: data.KeySchemeRSASSA_PSS_SHA256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+}
+
+func (s *rsaSigner) SignMessage(message []byte) ([]byte, error) {
+ hash := sha256.Sum256(message)
+ return rsa.SignPSS(rand.Reader, s.PrivateKey, crypto.SHA256, hash[:], &rsa.PSSOptions{})
+}
+
+func (s *rsaSigner) ContainsID(id string) bool {
+ return s.PublicData().ContainsID(id)
+}
+
+func (s *rsaSigner) MarshalPrivateKey() (*data.PrivateKey, error) {
+ priv := x509.MarshalPKCS1PrivateKey(s.PrivateKey)
+ pemKey := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: priv})
+ val, err := json.Marshal(rsaPrivateKeyValue{
+ Private: string(pemKey),
+ Public: &PKIXPublicKey{PublicKey: s.Public()},
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &data.PrivateKey{
+ Type: data.KeyTypeRSASSA_PSS_SHA256,
+ Scheme: data.KeySchemeRSASSA_PSS_SHA256,
+ Algorithms: data.HashAlgorithms,
+ Value: val,
+ }, nil
+}
+
+func (s *rsaSigner) UnmarshalPrivateKey(key *data.PrivateKey) error {
+ val := rsaPrivateKeyValue{}
+ if err := json.Unmarshal(key.Value, &val); err != nil {
+ return err
+ }
+ block, _ := pem.Decode([]byte(val.Private))
+ if block == nil {
+ return errors.New("invalid PEM value")
+ }
+ if block.Type != "RSA PRIVATE KEY" {
+ return fmt.Errorf("invalid block type: %s", block.Type)
+ }
+ k, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+ if _, err := json.Marshal(rsaVerifier{
+ PublicKey: &PKIXPublicKey{PublicKey: k.Public()}}); err != nil {
+ return fmt.Errorf("invalid public key: %s", err)
+ }
+
+ s.PrivateKey = k
+ return nil
+}
+
+func GenerateRsaKey() (*rsaSigner, error) {
+ privkey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return nil, err
+ }
+ return &rsaSigner{privkey}, nil
+}
diff --git a/pkg/keys/rsa_test.go b/pkg/keys/rsa_test.go
new file mode 100644
index 0000000..7352000
--- /dev/null
+++ b/pkg/keys/rsa_test.go
@@ -0,0 +1,125 @@
+package keys
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "io"
+
+ "github.com/theupdateframework/go-tuf/data"
+ . "gopkg.in/check.v1"
+)
+
+type RsaSuite struct{}
+
+var _ = Suite(&RsaSuite{})
+
+func (RsaSuite) TestSignVerify(c *C) {
+ signer, err := GenerateRsaKey()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+ publicData := signer.PublicData()
+ pubKey, err := GetVerifier(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.Verify(msg, sig), IsNil)
+}
+
+func (RsaSuite) TestRSAVerifyMismatchMessage(c *C) {
+ signer, err := GenerateRsaKey()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+ publicData := signer.PublicData()
+ pubKey, err := GetVerifier(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.Verify([]byte("notfoo"), sig), ErrorMatches, "crypto/rsa: verification error")
+}
+
+func (RsaSuite) TestRSAVerifyMismatchPubKey(c *C) {
+ signer, err := GenerateRsaKey()
+ c.Assert(err, IsNil)
+ msg := []byte("foo")
+ sig, err := signer.SignMessage(msg)
+ c.Assert(err, IsNil)
+
+ signerNew, err := GenerateRsaKey()
+ c.Assert(err, IsNil)
+
+ pubKey, err := GetVerifier(signerNew.PublicData())
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.Verify([]byte("notfoo"), sig), ErrorMatches, "crypto/rsa: verification error")
+}
+
+func (RsaSuite) TestMarshalUnmarshalPublicKey(c *C) {
+ signer, err := GenerateRsaKey()
+ c.Assert(err, IsNil)
+ publicData := signer.PublicData()
+ pubKey, err := GetVerifier(publicData)
+ c.Assert(err, IsNil)
+ c.Assert(pubKey.MarshalPublicKey(), DeepEquals, publicData)
+}
+
+func (RsaSuite) TestMarshalUnmarshalPrivateKey(c *C) {
+ signer, err := GenerateRsaKey()
+ c.Assert(err, IsNil)
+ privateData, err := signer.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ c.Assert(privateData.Type, Equals, data.KeyTypeRSASSA_PSS_SHA256)
+ c.Assert(privateData.Scheme, Equals, data.KeySchemeRSASSA_PSS_SHA256)
+ c.Assert(privateData.Algorithms, DeepEquals, data.HashAlgorithms)
+ s, err := GetSigner(privateData)
+ c.Assert(err, IsNil)
+ c.Assert(s, DeepEquals, signer)
+}
+
+func (ECDSASuite) TestUnmarshalRSA_Invalid(c *C) {
+ badKeyValue, err := json.Marshal(true)
+ c.Assert(err, IsNil)
+
+ badKey := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: badKeyValue,
+ }
+ verifier := NewEcdsaVerifier()
+ c.Assert(verifier.UnmarshalPublicKey(badKey), ErrorMatches, "json: cannot unmarshal.*")
+}
+
+func (ECDSASuite) TestUnmarshalRSAPublicKey(c *C) {
+ priv, err := GenerateRsaKey()
+ c.Assert(err, IsNil)
+
+ signer := &rsaSigner{priv.PrivateKey}
+ goodKey := signer.PublicData()
+
+ verifier := newRsaVerifier()
+ c.Assert(verifier.UnmarshalPublicKey(goodKey), IsNil)
+}
+
+func (ECDSASuite) TestUnmarshalRSA_TooLongContent(c *C) {
+ randomSeed := make([]byte, MaxJSONKeySize)
+ _, err := io.ReadFull(rand.Reader, randomSeed)
+ c.Assert(err, IsNil)
+
+ tooLongPayload, err := json.Marshal(
+ &ed25519Verifier{
+ PublicKey: data.HexBytes(hex.EncodeToString(randomSeed)),
+ },
+ )
+ c.Assert(err, IsNil)
+
+ badKey := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: tooLongPayload,
+ }
+ verifier := newRsaVerifier()
+ err = verifier.UnmarshalPublicKey(badKey)
+ c.Assert(errors.Is(err, io.ErrUnexpectedEOF), Equals, true)
+}
diff --git a/pkg/targets/delegation.go b/pkg/targets/delegation.go
new file mode 100644
index 0000000..dce6171
--- /dev/null
+++ b/pkg/targets/delegation.go
@@ -0,0 +1,102 @@
+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
+ Delegatee data.DelegatedRole
+ DB *verify.DB
+}
+
+type delegationsIterator struct {
+ stack []Delegation
+ target string
+ visitedRoles map[string]struct{}
+ parents map[string]string
+}
+
+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, 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",
+ KeyIDs: sets.StringSetToSlice(targetsRole.KeyIDs),
+ Threshold: targetsRole.Threshold,
+ },
+ DB: topLevelKeysDB,
+ },
+ },
+ visitedRoles: make(map[string]struct{}),
+ parents: make(map[string]string),
+ }
+ return i, nil
+}
+
+func (d *delegationsIterator) Next() (value Delegation, ok bool) {
+ if len(d.stack) == 0 {
+ return Delegation{}, false
+ }
+ delegation := d.stack[len(d.stack)-1]
+ d.stack = d.stack[:len(d.stack)-1]
+
+ // 5.6.7.1: If this role has been visited before, then skip this role (so
+ // that cycles in the delegation graph are avoided).
+ roleName := delegation.Delegatee.Name
+ if _, ok := d.visitedRoles[roleName]; ok {
+ return d.Next()
+ }
+ d.visitedRoles[roleName] = struct{}{}
+
+ // 5.6.7.2 trim delegations to visit, only the current role and its delegations
+ // will be considered
+ // https://github.com/theupdateframework/specification/issues/168
+ if delegation.Delegatee.Terminating {
+ // Empty the stack.
+ d.stack = d.stack[0:0]
+ }
+ return delegation, true
+}
+
+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.
+ r := roles[i]
+ matchesPath, err := r.MatchesPath(d.target)
+ if err != nil {
+ return err
+ }
+ if matchesPath {
+ delegation := Delegation{
+ Delegator: delegator,
+ Delegatee: r,
+ DB: db,
+ }
+ d.stack = append(d.stack, delegation)
+ d.parents[r.Name] = delegator
+ }
+ }
+
+ return nil
+}
+
+func (d *delegationsIterator) Parent(role string) string {
+ return d.parents[role]
+}
diff --git a/pkg/targets/delegation_test.go b/pkg/targets/delegation_test.go
new file mode 100644
index 0000000..2e0c42b
--- /dev/null
+++ b/pkg/targets/delegation_test.go
@@ -0,0 +1,252 @@
+package targets
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/verify"
+)
+
+var (
+ defaultPathPatterns = []string{"tmp", "*"}
+ noMatchPathPatterns = []string{"vars", "null"}
+)
+
+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
+ file string
+ 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, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "b": {
+ {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "c": {
+ {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "e": {
+ {Name: "f", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "g", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "g": {
+ {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: "",
+ resultOrder: []string{"targets", "b", "c", "d", "e", "f", "g", "h", "i", "j"},
+ },
+ {
+ testName: "terminated in b",
+ roles: map[string][]data.DelegatedRole{
+ "targets": {
+ {Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs, Terminating: true},
+ {Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "b": {
+ {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ },
+ file: "",
+ resultOrder: []string{"targets", "b", "c", "d"},
+ },
+ {
+ testName: "path does not match b",
+ roles: map[string][]data.DelegatedRole{
+ "targets": {
+ {Name: "b", Paths: noMatchPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "b": {
+ {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ },
+ file: "",
+ resultOrder: []string{"targets", "e"},
+ },
+ {
+ testName: "path does not match b - path prefixes",
+ roles: map[string][]data.DelegatedRole{
+ "targets": {
+ {Name: "b", PathHashPrefixes: []string{"33472a4909"}, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "c", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633"}, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "c": {
+
+ {Name: "d", PathHashPrefixes: []string{"8baf"}, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "e", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633472a49096ed87f8f764bd597831eac371f40ac39"}, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ },
+ file: "/e/f/g.txt",
+ resultOrder: []string{"targets", "c", "e"},
+ },
+ {
+ testName: "err paths and pathHashPrefixes are set",
+ roles: map[string][]data.DelegatedRole{
+ "targets": {
+ {Name: "b", Paths: defaultPathPatterns, PathHashPrefixes: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "b": {},
+ },
+ file: "",
+ resultOrder: []string{"targets"},
+ err: data.ErrPathsAndPathHashesSet,
+ },
+ {
+ testName: "cycle avoided 1",
+ roles: map[string][]data.DelegatedRole{
+ "targets": {
+ {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: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ },
+ file: "",
+ resultOrder: []string{"targets", "a", "b", "d", "e"},
+ },
+ {
+ testName: "cycle avoided 2",
+ roles: map[string][]data.DelegatedRole{
+ "targets": {
+ {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: "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, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ },
+ file: "",
+ resultOrder: []string{"targets", "a", "b", "c"},
+ },
+ {
+ testName: "diamond delegation",
+ roles: map[string][]data.DelegatedRole{
+ "targets": {
+ {Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "b": {
+ {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "c": {
+ {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ },
+ file: "",
+ resultOrder: []string{"targets", "b", "d", "c"},
+ },
+ {
+ testName: "simple cycle",
+ roles: map[string][]data.DelegatedRole{
+ "targets": {
+ {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ "a": {
+ {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs},
+ },
+ },
+ file: "",
+ resultOrder: []string{"targets", "a"},
+ },
+ }
+
+ for _, tt := range iteratorTests {
+ t.Run(tt.testName, func(t *testing.T) {
+ 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
+ }
+
+ 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/pkg/targets/hash_bins.go b/pkg/targets/hash_bins.go
new file mode 100644
index 0000000..95f4405
--- /dev/null
+++ b/pkg/targets/hash_bins.go
@@ -0,0 +1,113 @@
+package targets
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+const MinDelegationHashPrefixBitLen = 1
+const MaxDelegationHashPrefixBitLen = 32
+
+// hexEncode formats x as a hex string. The hex string is left padded with
+// zeros to padWidth, if necessary.
+func hexEncode(x uint64, padWidth int) string {
+ // Benchmarked to be more than 10x faster than padding with Sprintf.
+ s := strconv.FormatUint(x, 16)
+ if len(s) >= padWidth {
+ return s
+ }
+ return strings.Repeat("0", padWidth-len(s)) + s
+}
+
+const bitsPerHexDigit = 4
+
+// numHexDigits returns the number of hex digits required to encode the given
+// number of bits.
+func numHexDigits(numBits int) int {
+ // ceil(numBits / bitsPerHexDigit)
+ return ((numBits - 1) / bitsPerHexDigit) + 1
+}
+
+// HashBins represents an ordered list of hash bin target roles, which together
+// partition the space of target path hashes equal-sized buckets based on path
+// has prefix.
+type HashBins struct {
+ rolePrefix string
+ bitLen int
+ hexDigitLen int
+
+ numBins uint64
+ numPrefixesPerBin uint64
+}
+
+// NewHashBins creates a HashBins partitioning with 2^bitLen buckets.
+func NewHashBins(rolePrefix string, bitLen int) (*HashBins, error) {
+ if bitLen < MinDelegationHashPrefixBitLen || bitLen > MaxDelegationHashPrefixBitLen {
+ return nil, fmt.Errorf("bitLen is out of bounds, should be between %v and %v inclusive", MinDelegationHashPrefixBitLen, MaxDelegationHashPrefixBitLen)
+ }
+
+ hexDigitLen := numHexDigits(bitLen)
+ numBins := uint64(1) << bitLen
+
+ numPrefixesTotal := uint64(1) << (bitsPerHexDigit * hexDigitLen)
+ numPrefixesPerBin := numPrefixesTotal / numBins
+
+ return &HashBins{
+ rolePrefix: rolePrefix,
+ bitLen: bitLen,
+ hexDigitLen: hexDigitLen,
+ numBins: numBins,
+ numPrefixesPerBin: numPrefixesPerBin,
+ }, nil
+}
+
+// NumBins returns the number of hash bin partitions.
+func (hb *HashBins) NumBins() uint64 {
+ return hb.numBins
+}
+
+// GetBin returns the HashBin at index i, or nil if i is out of bounds.
+func (hb *HashBins) GetBin(i uint64) *HashBin {
+ if i >= hb.numBins {
+ return nil
+ }
+
+ return &HashBin{
+ rolePrefix: hb.rolePrefix,
+ hexDigitLen: hb.hexDigitLen,
+ first: i * hb.numPrefixesPerBin,
+ last: ((i + 1) * hb.numPrefixesPerBin) - 1,
+ }
+}
+
+// HashBin represents a hex prefix range. First should be less than Last.
+type HashBin struct {
+ rolePrefix string
+ hexDigitLen int
+ first uint64
+ last uint64
+}
+
+// RoleName returns the name of the role that signs for the HashBin.
+func (b *HashBin) RoleName() string {
+ if b.first == b.last {
+ return b.rolePrefix + hexEncode(b.first, b.hexDigitLen)
+ }
+
+ return b.rolePrefix + hexEncode(b.first, b.hexDigitLen) + "-" + hexEncode(b.last, b.hexDigitLen)
+}
+
+// HashPrefixes returns a slice of all hash prefixes in the bin.
+func (b *HashBin) HashPrefixes() []string {
+ n := int(b.last - b.first + 1)
+ ret := make([]string, int(n))
+
+ x := b.first
+ for i := 0; i < n; i++ {
+ ret[i] = hexEncode(x, b.hexDigitLen)
+ x++
+ }
+
+ return ret
+}
diff --git a/pkg/targets/hash_bins_test.go b/pkg/targets/hash_bins_test.go
new file mode 100644
index 0000000..dce2ab8
--- /dev/null
+++ b/pkg/targets/hash_bins_test.go
@@ -0,0 +1,119 @@
+package targets
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func BenchmarkHexEncode1(b *testing.B) {
+ for n := 0; n <= b.N; n++ {
+ for x := uint64(0); x <= 0xf; x += 1 {
+ hexEncode(x, 1)
+ }
+ }
+}
+
+func BenchmarkHexEncode4(b *testing.B) {
+ for n := 0; n <= b.N; n++ {
+ for x := uint64(0); x <= 0xffff; x += 1 {
+ hexEncode(x, 4)
+ }
+ }
+}
+
+func TestHashBin(t *testing.T) {
+ tcs := []struct {
+ hb *HashBin
+ roleName string
+ hashPrefixes []string
+ }{
+ {
+ hb: &HashBin{
+ rolePrefix: "abc_",
+ hexDigitLen: 1,
+ first: 0x0,
+ last: 0x7,
+ },
+ roleName: "abc_0-7",
+ hashPrefixes: []string{
+ "0", "1", "2", "3", "4", "5", "6", "7",
+ },
+ },
+ {
+ hb: &HashBin{
+ rolePrefix: "abc_",
+ hexDigitLen: 2,
+ first: 0x0,
+ last: 0xf,
+ },
+ roleName: "abc_00-0f",
+ hashPrefixes: []string{
+ "00", "01", "02", "03", "04", "05", "06", "07",
+ "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
+ },
+ },
+ {
+ hb: &HashBin{
+ rolePrefix: "cba_",
+ hexDigitLen: 4,
+ first: 0xcd,
+ last: 0xcf,
+ },
+ roleName: "cba_00cd-00cf",
+ hashPrefixes: []string{"00cd", "00ce", "00cf"},
+ },
+ {
+ hb: &HashBin{
+ rolePrefix: "cba_",
+ hexDigitLen: 3,
+ first: 0xc1,
+ last: 0xc1,
+ },
+ roleName: "cba_0c1",
+ hashPrefixes: []string{"0c1"},
+ },
+ }
+
+ for i, tc := range tcs {
+ assert.Equalf(t, tc.roleName, tc.hb.RoleName(), "test case %v: RoleName()", i)
+ assert.Equalf(t, tc.hashPrefixes, tc.hb.HashPrefixes(), "test case %v: HashPrefixes()", i)
+ }
+}
+
+func TestHashBins(t *testing.T) {
+ tcs := []struct {
+ bitLen int
+ roleNames []string
+ }{
+ {1, []string{"0-7", "8-f"}},
+ {2, []string{"0-3", "4-7", "8-b", "c-f"}},
+ {3, []string{"0-1", "2-3", "4-5", "6-7", "8-9", "a-b", "c-d", "e-f"}},
+ {4, []string{
+ "0", "1", "2", "3", "4", "5", "6", "7",
+ "8", "9", "a", "b", "c", "d", "e", "f",
+ }},
+ {5, []string{
+ "00-07", "08-0f", "10-17", "18-1f", "20-27", "28-2f", "30-37", "38-3f",
+ "40-47", "48-4f", "50-57", "58-5f", "60-67", "68-6f", "70-77", "78-7f",
+ "80-87", "88-8f", "90-97", "98-9f", "a0-a7", "a8-af", "b0-b7", "b8-bf",
+ "c0-c7", "c8-cf", "d0-d7", "d8-df", "e0-e7", "e8-ef", "f0-f7", "f8-ff",
+ }},
+ }
+ for i, tc := range tcs {
+ got := []string{}
+ hbs, err := NewHashBins("", tc.bitLen)
+ assert.NoError(t, err)
+ n := hbs.NumBins()
+ for i := uint64(0); i < n; i += 1 {
+ hb := hbs.GetBin(i)
+ got = append(got, hb.RoleName())
+ }
+ assert.Equalf(t, tc.roleNames, got, "test case %v", i)
+ }
+
+ _, err := NewHashBins("", 0)
+ assert.Error(t, err)
+ _, err = NewHashBins("", 33)
+ assert.Error(t, err)
+}
diff --git a/repo.go b/repo.go
new file mode 100644
index 0000000..db2ac66
--- /dev/null
+++ b/repo.go
@@ -0,0 +1,1695 @@
+package tuf
+
+import (
+ "bytes"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "path"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/internal/roles"
+ "github.com/theupdateframework/go-tuf/internal/sets"
+ "github.com/theupdateframework/go-tuf/internal/signer"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+ "github.com/theupdateframework/go-tuf/pkg/targets"
+ "github.com/theupdateframework/go-tuf/sign"
+ "github.com/theupdateframework/go-tuf/util"
+ "github.com/theupdateframework/go-tuf/verify"
+)
+
+const (
+ // The maximum number of delegations to visit while traversing the delegations graph.
+ defaultMaxDelegations = 32
+)
+
+// topLevelMetadata determines the order signatures are verified when committing.
+var topLevelMetadata = []string{
+ "root.json",
+ "targets.json",
+ "snapshot.json",
+ "timestamp.json",
+}
+
+// TargetsWalkFunc is a function of a target path name and a target payload used to
+// execute some function on each staged target file. For example, it may normalize path
+// names and generate target file metadata with additional custom metadata.
+type TargetsWalkFunc func(path string, target io.Reader) error
+
+type Repo struct {
+ local LocalStore
+ hashAlgorithms []string
+ meta map[string]json.RawMessage
+ prefix string
+ indent string
+ logger *log.Logger
+}
+
+type RepoOpts func(r *Repo)
+
+func WithLogger(logger *log.Logger) RepoOpts {
+ return func(r *Repo) {
+ r.logger = logger
+ }
+}
+
+func WithHashAlgorithms(hashAlgorithms ...string) RepoOpts {
+ return func(r *Repo) {
+ r.hashAlgorithms = hashAlgorithms
+ }
+}
+
+func WithPrefix(prefix string) RepoOpts {
+ return func(r *Repo) {
+ r.prefix = prefix
+ }
+}
+
+func WithIndex(indent string) RepoOpts {
+ return func(r *Repo) {
+ r.indent = indent
+ }
+}
+
+func NewRepo(local LocalStore, hashAlgorithms ...string) (*Repo, error) {
+ return NewRepoIndent(local, "", "", hashAlgorithms...)
+}
+
+func NewRepoIndent(local LocalStore, prefix string, indent string,
+ hashAlgorithms ...string) (*Repo, error) {
+ r := &Repo{
+ local: local,
+ hashAlgorithms: hashAlgorithms,
+ prefix: prefix,
+ indent: indent,
+ logger: log.New(io.Discard, "", 0),
+ }
+
+ var err error
+ r.meta, err = local.GetMeta()
+ if err != nil {
+ return nil, err
+ }
+ return r, nil
+}
+
+func NewRepoWithOpts(local LocalStore, opts ...RepoOpts) (*Repo, error) {
+ r, err := NewRepo(local)
+ if err != nil {
+ return nil, err
+ }
+ for _, opt := range opts {
+ opt(r)
+ }
+ return r, nil
+}
+
+func (r *Repo) Init(consistentSnapshot bool) error {
+ t, err := r.topLevelTargets()
+ if err != nil {
+ return err
+ }
+ if len(t.Targets) > 0 {
+ return ErrInitNotAllowed
+ }
+ root := data.NewRoot()
+ root.ConsistentSnapshot = consistentSnapshot
+ // Set root version to 1 for a new root.
+ root.Version = 1
+ if err = r.setMeta("root.json", root); err != nil {
+ return err
+ }
+
+ t.Version = 1
+ if err = r.setMeta("targets.json", t); err != nil {
+ return err
+ }
+
+ r.logger.Println("Repository initialized")
+ return nil
+}
+
+func (r *Repo) topLevelKeysDB() (*verify.DB, error) {
+ db := verify.NewDB()
+ root, err := r.root()
+ if err != nil {
+ return nil, err
+ }
+ for id, k := range root.Keys {
+ if err := db.AddKey(id, k); err != nil {
+ return nil, err
+ }
+ }
+ for name, role := range root.Roles {
+ if err := db.AddRole(name, role); err != nil {
+ return nil, err
+ }
+ }
+ return db, nil
+}
+
+func (r *Repo) root() (*data.Root, error) {
+ rootJSON, ok := r.meta["root.json"]
+ if !ok {
+ return data.NewRoot(), nil
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(rootJSON, s); err != nil {
+ return nil, err
+ }
+ root := &data.Root{}
+ if err := json.Unmarshal(s.Signed, root); err != nil {
+ return nil, err
+ }
+ return root, nil
+}
+
+func (r *Repo) snapshot() (*data.Snapshot, error) {
+ snapshotJSON, ok := r.meta["snapshot.json"]
+ if !ok {
+ return data.NewSnapshot(), nil
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(snapshotJSON, s); err != nil {
+ return nil, err
+ }
+ snapshot := &data.Snapshot{}
+ if err := json.Unmarshal(s.Signed, snapshot); err != nil {
+ return nil, err
+ }
+ return snapshot, nil
+}
+
+func (r *Repo) RootVersion() (int64, error) {
+ root, err := r.root()
+ if err != nil {
+ return -1, err
+ }
+ return root.Version, nil
+}
+
+func (r *Repo) GetThreshold(keyRole string) (int, error) {
+ if roles.IsDelegatedTargetsRole(keyRole) {
+ // The signature threshold for a delegated targets role
+ // depends on the incoming delegation edge.
+ return -1, ErrInvalidRole{keyRole, "only thresholds for top-level roles supported"}
+ }
+ root, err := r.root()
+ if err != nil {
+ return -1, err
+ }
+ role, ok := root.Roles[keyRole]
+ if !ok {
+ return -1, ErrInvalidRole{keyRole, "role missing from root metadata"}
+ }
+
+ return role.Threshold, nil
+}
+
+func (r *Repo) SetThreshold(keyRole string, t int) error {
+ if roles.IsDelegatedTargetsRole(keyRole) {
+ // The signature threshold for a delegated targets role
+ // depends on the incoming delegation edge.
+ return ErrInvalidRole{keyRole, "only thresholds for top-level roles supported"}
+ }
+ root, err := r.root()
+ if err != nil {
+ return err
+ }
+ role, ok := root.Roles[keyRole]
+ if !ok {
+ return ErrInvalidRole{keyRole, "role missing from root metadata"}
+ }
+ if role.Threshold == t {
+ // Change was a no-op.
+ return nil
+ }
+ role.Threshold = t
+ if !r.local.FileIsStaged("root.json") {
+ root.Version++
+ }
+ return r.setMeta("root.json", root)
+}
+
+func (r *Repo) Targets() (data.TargetFiles, error) {
+ targets, err := r.topLevelTargets()
+ if err != nil {
+ return nil, err
+ }
+ return targets.Targets, nil
+}
+
+func (r *Repo) SetTargetsVersion(v int64) error {
+ t, err := r.topLevelTargets()
+ if err != nil {
+ return err
+ }
+ t.Version = v
+ return r.setMeta("targets.json", t)
+}
+
+func (r *Repo) TargetsVersion() (int64, error) {
+ t, err := r.topLevelTargets()
+ if err != nil {
+ return -1, err
+ }
+ return t.Version, nil
+}
+
+func (r *Repo) SetTimestampVersion(v int64) error {
+ ts, err := r.timestamp()
+ if err != nil {
+ return err
+ }
+ ts.Version = v
+ return r.setMeta("timestamp.json", ts)
+}
+
+func (r *Repo) TimestampVersion() (int64, error) {
+ ts, err := r.timestamp()
+ if err != nil {
+ return -1, err
+ }
+ return ts.Version, nil
+}
+
+func (r *Repo) SetSnapshotVersion(v int64) error {
+ s, err := r.snapshot()
+ if err != nil {
+ return err
+ }
+
+ s.Version = v
+ return r.setMeta("snapshot.json", s)
+}
+
+func (r *Repo) SnapshotVersion() (int64, error) {
+ s, err := r.snapshot()
+ if err != nil {
+ return -1, err
+ }
+ return s.Version, nil
+}
+
+func (r *Repo) topLevelTargets() (*data.Targets, error) {
+ return r.targets("targets")
+}
+
+func (r *Repo) targets(metaName string) (*data.Targets, error) {
+ targetsJSON, ok := r.meta[metaName+".json"]
+ if !ok {
+ return data.NewTargets(), nil
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(targetsJSON, s); err != nil {
+ return nil, fmt.Errorf("error unmarshalling for targets %q: %w", metaName, err)
+ }
+ targets := &data.Targets{}
+ if err := json.Unmarshal(s.Signed, targets); err != nil {
+ return nil, fmt.Errorf("error unmarshalling signed data for targets %q: %w", metaName, err)
+ }
+ return targets, nil
+}
+
+func (r *Repo) timestamp() (*data.Timestamp, error) {
+ timestampJSON, ok := r.meta["timestamp.json"]
+ if !ok {
+ return data.NewTimestamp(), nil
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(timestampJSON, s); err != nil {
+ return nil, err
+ }
+ timestamp := &data.Timestamp{}
+ if err := json.Unmarshal(s.Signed, timestamp); err != nil {
+ return nil, err
+ }
+ return timestamp, nil
+}
+
+func (r *Repo) ChangePassphrase(keyRole string) error {
+ if p, ok := r.local.(PassphraseChanger); ok {
+ return p.ChangePassphrase(keyRole)
+ }
+
+ return ErrChangePassphraseNotSupported
+}
+
+func (r *Repo) GenKey(role string) ([]string, error) {
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ return r.GenKeyWithExpires(role, data.DefaultExpires(role))
+}
+
+func (r *Repo) GenKeyWithExpires(keyRole string, expires time.Time) (keyids []string, err error) {
+ return r.GenKeyWithSchemeAndExpires(keyRole, expires, data.KeySchemeEd25519)
+}
+
+func (r *Repo) GenKeyWithSchemeAndExpires(role string, expires time.Time, keyScheme data.KeyScheme) ([]string, error) {
+ var signer keys.Signer
+ var err error
+ switch keyScheme {
+ case data.KeySchemeEd25519:
+ signer, err = keys.GenerateEd25519Key()
+ case data.KeySchemeECDSA_SHA2_P256:
+ signer, err = keys.GenerateEcdsaKey()
+ case data.KeySchemeRSASSA_PSS_SHA256:
+ signer, err = keys.GenerateRsaKey()
+ default:
+ return nil, errors.New("unknown key type")
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ if err = r.AddPrivateKeyWithExpires(role, signer, expires); err != nil {
+ return nil, err
+ }
+ return signer.PublicData().IDs(), nil
+}
+
+func (r *Repo) AddPrivateKey(role string, signer keys.Signer) error {
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ return r.AddPrivateKeyWithExpires(role, signer, data.DefaultExpires(role))
+}
+
+func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expires time.Time) error {
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ if roles.IsDelegatedTargetsRole(keyRole) {
+ return ErrInvalidRole{keyRole, "only support adding keys for top-level roles"}
+ }
+
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ // Must add signer before adding verification key, so
+ // root.json can be signed when a new root key is added.
+ if err := r.local.SaveSigner(keyRole, signer); err != nil {
+ return err
+ }
+
+ if err := r.AddVerificationKeyWithExpiration(keyRole, signer.PublicData(), expires); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (r *Repo) AddVerificationKey(keyRole string, pk *data.PublicKey) error {
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ return r.AddVerificationKeyWithExpiration(keyRole, pk, data.DefaultExpires(keyRole))
+}
+
+func (r *Repo) AddVerificationKeyWithExpiration(keyRole string, pk *data.PublicKey, expires time.Time) error {
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ if roles.IsDelegatedTargetsRole(keyRole) {
+ return ErrInvalidRole{
+ Role: keyRole,
+ Reason: "only top-level targets roles are supported",
+ }
+ }
+
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ root, err := r.root()
+ if err != nil {
+ return err
+ }
+
+ role, ok := root.Roles[keyRole]
+ if !ok {
+ role = &data.Role{KeyIDs: []string{}, Threshold: 1}
+ root.Roles[keyRole] = role
+ }
+ changed := false
+ if role.AddKeyIDs(pk.IDs()) {
+ changed = true
+ }
+
+ if root.AddKey(pk) {
+ changed = true
+ }
+
+ if !changed {
+ return nil
+ }
+
+ root.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged("root.json") {
+ root.Version++
+ }
+
+ return r.setMeta("root.json", root)
+}
+
+func validExpires(expires time.Time) bool {
+ return time.Until(expires) > 0
+}
+
+func (r *Repo) RootKeys() ([]*data.PublicKey, error) {
+ root, err := r.root()
+ if err != nil {
+ return nil, err
+ }
+ role, ok := root.Roles["root"]
+ if !ok {
+ return nil, nil
+ }
+
+ // We might have multiple key ids that correspond to the same key, so
+ // make sure we only return unique keys.
+ seen := make(map[string]struct{})
+ rootKeys := []*data.PublicKey{}
+ for _, id := range role.KeyIDs {
+ key, ok := root.Keys[id]
+ if !ok {
+ return nil, fmt.Errorf("tuf: invalid root metadata")
+ }
+ found := false
+ if _, ok := seen[id]; ok {
+ found = true
+ break
+ }
+ if !found {
+ for _, id := range key.IDs() {
+ seen[id] = struct{}{}
+ }
+ rootKeys = append(rootKeys, key)
+ }
+ }
+ return rootKeys, nil
+}
+
+func (r *Repo) RevokeKey(role, id string) error {
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ return r.RevokeKeyWithExpires(role, id, data.DefaultExpires("root"))
+}
+
+func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error {
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ if roles.IsDelegatedTargetsRole(keyRole) {
+ return ErrInvalidRole{keyRole, "only revocations for top-level roles supported"}
+ }
+
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ root, err := r.root()
+ if err != nil {
+ return err
+ }
+
+ key, ok := root.Keys[id]
+ if !ok {
+ return ErrKeyNotFound{keyRole, id}
+ }
+
+ role, ok := root.Roles[keyRole]
+ if !ok {
+ return ErrKeyNotFound{keyRole, id}
+ }
+
+ // Create a list of filtered key IDs that do not contain the revoked key IDs.
+ filteredKeyIDs := make([]string, 0, len(role.KeyIDs))
+
+ // There may be multiple keyids that correspond to this key, so
+ // filter all of them out.
+ for _, keyID := range role.KeyIDs {
+ if !key.ContainsID(keyID) {
+ filteredKeyIDs = append(filteredKeyIDs, keyID)
+ }
+ }
+ if len(filteredKeyIDs) == len(role.KeyIDs) {
+ return ErrKeyNotFound{keyRole, id}
+ }
+ role.KeyIDs = filteredKeyIDs
+ root.Roles[keyRole] = role
+
+ // Only delete the key from root.Keys if the key is no longer in use by
+ // any other role.
+ key_in_use := false
+ for _, role := range root.Roles {
+ for _, keyID := range role.KeyIDs {
+ if key.ContainsID(keyID) {
+ key_in_use = true
+ }
+ }
+ }
+ if !key_in_use {
+ for _, keyID := range key.IDs() {
+ delete(root.Keys, keyID)
+ }
+ }
+ root.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged("root.json") {
+ root.Version++
+ }
+
+ err = r.setMeta("root.json", root)
+ if err == nil {
+ r.logger.Println("Revoked", keyRole, "key with ID", id, "in root metadata")
+ }
+ return err
+}
+
+// AddDelegatedRole is equivalent to AddDelegatedRoleWithExpires, but
+// with a default expiration time.
+func (r *Repo) AddDelegatedRole(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey) error {
+ return r.AddDelegatedRoleWithExpires(delegator, delegatedRole, keys, data.DefaultExpires("targets"))
+}
+
+// AddDelegatedRoleWithExpires adds a delegation from the delegator to the
+// role specified in the role argument. Key IDs referenced in role.KeyIDs
+// should have corresponding Key entries in the keys argument. New metadata is
+// written with the given expiration time.
+func (r *Repo) AddDelegatedRoleWithExpires(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey, expires time.Time) error {
+ expires = expires.Round(time.Second)
+
+ t, err := r.targets(delegator)
+ if err != nil {
+ return fmt.Errorf("error getting delegator (%q) metadata: %w", delegator, err)
+ }
+
+ if t.Delegations == nil {
+ t.Delegations = &data.Delegations{}
+ t.Delegations.Keys = make(map[string]*data.PublicKey)
+ }
+
+ for _, keyID := range delegatedRole.KeyIDs {
+ for _, key := range keys {
+ if key.ContainsID(keyID) {
+ t.Delegations.Keys[keyID] = key
+ break
+ }
+ }
+ }
+
+ for _, r := range t.Delegations.Roles {
+ if r.Name == delegatedRole.Name {
+ return fmt.Errorf("role: %s is already delegated to by %s", delegatedRole.Name, r.Name)
+ }
+ }
+ t.Delegations.Roles = append(t.Delegations.Roles, delegatedRole)
+ t.Expires = expires
+
+ delegatorFile := delegator + ".json"
+ if !r.local.FileIsStaged(delegatorFile) {
+ t.Version++
+ }
+
+ err = r.setMeta(delegatorFile, t)
+ if err != nil {
+ return fmt.Errorf("error setting metadata for %q: %w", delegatorFile, err)
+ }
+
+ delegatee := delegatedRole.Name
+ dt, err := r.targets(delegatee)
+ if err != nil {
+ return fmt.Errorf("error getting delegatee (%q) metadata: %w", delegatee, err)
+ }
+ dt.Expires = expires
+
+ delegateeFile := delegatee + ".json"
+ if !r.local.FileIsStaged(delegateeFile) {
+ dt.Version++
+ }
+
+ err = r.setMeta(delegateeFile, dt)
+ if err != nil {
+ return fmt.Errorf("error setting metadata for %q: %w", delegateeFile, err)
+ }
+
+ return nil
+}
+
+// AddDelegatedRolesForPathHashBins is equivalent to
+// AddDelegatedRolesForPathHashBinsWithExpires, but with a default
+// expiration time.
+func (r *Repo) AddDelegatedRolesForPathHashBins(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int) error {
+ return r.AddDelegatedRolesForPathHashBinsWithExpires(delegator, bins, keys, threshold, data.DefaultExpires("targets"))
+}
+
+// AddDelegatedRolesForPathHashBinsWithExpires adds delegations to the
+// delegator role for the given hash bins configuration. New metadata is
+// written with the given expiration time.
+func (r *Repo) AddDelegatedRolesForPathHashBinsWithExpires(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int, expires time.Time) error {
+ keyIDs := []string{}
+ for _, key := range keys {
+ keyIDs = append(keyIDs, key.IDs()...)
+ }
+
+ n := bins.NumBins()
+ for i := uint64(0); i < n; i += 1 {
+ bin := bins.GetBin(i)
+ name := bin.RoleName()
+ err := r.AddDelegatedRoleWithExpires(delegator, data.DelegatedRole{
+ Name: name,
+ KeyIDs: sets.DeduplicateStrings(keyIDs),
+ PathHashPrefixes: bin.HashPrefixes(),
+ Threshold: threshold,
+ }, keys, expires)
+ if err != nil {
+ return fmt.Errorf("error adding delegation from %v to %v: %w", delegator, name, err)
+ }
+ }
+
+ return nil
+}
+
+// ResetTargetsDelegation is equivalent to ResetTargetsDelegationsWithExpires
+// with a default expiry time.
+func (r *Repo) ResetTargetsDelegations(delegator string) error {
+ return r.ResetTargetsDelegationsWithExpires(delegator, data.DefaultExpires("targets"))
+}
+
+// ResetTargetsDelegationsWithExpires removes all targets delegations from the
+// given delegator role. New metadata is written with the given expiration
+// time.
+func (r *Repo) ResetTargetsDelegationsWithExpires(delegator string, expires time.Time) error {
+ t, err := r.targets(delegator)
+ if err != nil {
+ return fmt.Errorf("error getting delegator (%q) metadata: %w", delegator, err)
+ }
+
+ t.Delegations = nil
+
+ t.Expires = expires.Round(time.Second)
+
+ delegatorFile := delegator + ".json"
+ if !r.local.FileIsStaged(delegatorFile) {
+ t.Version++
+ }
+
+ err = r.setMeta(delegatorFile, t)
+ if err != nil {
+ return fmt.Errorf("error setting metadata for %q: %w", delegatorFile, err)
+ }
+
+ return nil
+}
+
+func (r *Repo) jsonMarshal(v interface{}) ([]byte, error) {
+ if r.prefix == "" && r.indent == "" {
+ return json.Marshal(v)
+ }
+ return json.MarshalIndent(v, r.prefix, r.indent)
+}
+
+func (r *Repo) dbsForRole(role string) ([]*verify.DB, error) {
+ dbs := []*verify.DB{}
+
+ if roles.IsTopLevelRole(role) {
+ db, err := r.topLevelKeysDB()
+ if err != nil {
+ return nil, err
+ }
+ dbs = append(dbs, db)
+ } else {
+ ddbs, err := r.delegatorDBs(role)
+ if err != nil {
+ return nil, err
+ }
+
+ dbs = append(dbs, ddbs...)
+ }
+
+ return dbs, nil
+}
+
+func (r *Repo) signersForRole(role string) ([]keys.Signer, error) {
+ dbs, err := r.dbsForRole(role)
+ if err != nil {
+ return nil, err
+ }
+
+ signers := []keys.Signer{}
+ for _, db := range dbs {
+ ss, err := r.getSignersInDB(role, db)
+ if err != nil {
+ return nil, err
+ }
+
+ signers = append(signers, ss...)
+ }
+
+ return signers, nil
+}
+
+func (r *Repo) setMeta(roleFilename string, meta interface{}) error {
+ role := strings.TrimSuffix(roleFilename, ".json")
+
+ signers, err := r.signersForRole(role)
+ if err != nil {
+ return err
+ }
+
+ s, err := sign.Marshal(meta, signers...)
+ if err != nil {
+ return err
+ }
+ b, err := r.jsonMarshal(s)
+ if err != nil {
+ return err
+ }
+ r.meta[roleFilename] = b
+ return r.local.SetMeta(roleFilename, b)
+}
+
+// CanonicalizeAndSign canonicalizes the signed portion of signed, then signs it using the key(s) associated with role.
+//
+// It appends the signature to signed.
+//
+// It returns the total number of keys used for signing, 0 (along with
+// ErrNoKeys) if no keys were found, or -1 (along with an error) in error cases.
+func (r *Repo) CanonicalizeAndSign(role string, signed *data.Signed) (int, error) {
+ keys, err := r.signersForRole(role)
+ if err != nil {
+ return -1, err
+ }
+ if len(keys) == 0 {
+ return 0, ErrNoKeys{role}
+ }
+ for _, k := range keys {
+ if err = sign.Sign(signed, k); err != nil {
+ return -1, err
+ }
+ }
+ return len(keys), nil
+}
+
+// SignPayload canonicalizes the signed portion of payload, then signs it using the key(s) associated with role.
+//
+// It returns the total number of keys used for signing, 0 (along with
+// ErrNoKeys) if no keys were found, or -1 (along with an error) in error cases.
+//
+// DEPRECATED: please use CanonicalizeAndSign instead.
+func (r *Repo) SignPayload(role string, payload *data.Signed) (int, error) {
+ return r.CanonicalizeAndSign(role, payload)
+}
+
+// SignRaw signs the given (pre-canonicalized) payload using the key(s) associated with role.
+//
+// It returns the new data.Signatures.
+func (r *Repo) SignRaw(role string, payload []byte) ([]data.Signature, error) {
+ keys, err := r.signersForRole(role)
+ if err != nil {
+ return nil, err
+ }
+ if len(keys) == 0 {
+ return nil, ErrNoKeys{role}
+ }
+
+ allSigs := make([]data.Signature, 0, len(keys))
+ for _, k := range keys {
+ sigs, err := sign.MakeSignatures(payload, k)
+ if err != nil {
+ return nil, err
+ }
+ allSigs = append(allSigs, sigs...)
+ }
+ return allSigs, nil
+}
+
+func (r *Repo) Sign(roleFilename string) error {
+ signed, err := r.SignedMeta(roleFilename)
+ if err != nil {
+ return err
+ }
+
+ role := strings.TrimSuffix(roleFilename, ".json")
+ numKeys, err := r.SignPayload(role, signed)
+ if errors.Is(err, ErrNoKeys{role}) {
+ return ErrNoKeys{roleFilename}
+ } else if err != nil {
+ return err
+ }
+
+ b, err := r.jsonMarshal(signed)
+ if err != nil {
+ return err
+ }
+ r.meta[roleFilename] = b
+ err = r.local.SetMeta(roleFilename, b)
+ if err == nil {
+ r.logger.Println("Signed", roleFilename, "with", numKeys, "key(s)")
+ }
+ return err
+}
+
+// AddOrUpdateSignature allows users to add or update a signature generated with an external tool.
+// The name must be a valid metadata file name, like root.json.
+func (r *Repo) AddOrUpdateSignature(roleFilename string, signature data.Signature) error {
+ role := strings.TrimSuffix(roleFilename, ".json")
+
+ // Check key ID is in valid for the role.
+ dbs, err := r.dbsForRole(role)
+ if err != nil {
+ return err
+ }
+
+ if len(dbs) == 0 {
+ return ErrInvalidRole{role, "no trusted keys for role"}
+ }
+
+ s, err := r.SignedMeta(roleFilename)
+ if err != nil {
+ return err
+ }
+
+ keyInDB := false
+ validSig := false
+ for _, db := range dbs {
+ roleData := db.GetRole(role)
+ if roleData == nil {
+ return ErrInvalidRole{role, "role is not in verifier DB"}
+ }
+ if roleData.ValidKey(signature.KeyID) {
+ keyInDB = true
+
+ verifier, err := db.GetVerifier(signature.KeyID)
+ if err != nil {
+ continue
+ }
+
+ // Now check if this validly signed the metadata.
+ if err := verify.VerifySignature(s.Signed, signature.Signature,
+ verifier); err == nil {
+ validSig = true
+ break
+ }
+ }
+ }
+ if !keyInDB {
+ // This key was not delegated for the role in any delegatee.
+ return verify.ErrInvalidKey
+ }
+ if !validSig {
+ // The signature was invalid.
+ return verify.ErrInvalid
+ }
+
+ // Add or update signature.
+ signatures := make([]data.Signature, 0, len(s.Signatures)+1)
+ for _, sig := range s.Signatures {
+ if sig.KeyID != signature.KeyID {
+ signatures = append(signatures, sig)
+ }
+ }
+ signatures = append(signatures, signature)
+ s.Signatures = signatures
+
+ b, err := r.jsonMarshal(s)
+ if err != nil {
+ return err
+ }
+ r.meta[roleFilename] = b
+
+ return r.local.SetMeta(roleFilename, b)
+}
+
+// getSignersInDB returns available signing interfaces, sorted by key ID.
+//
+// Only keys contained in the keys db are returned (i.e. local keys which have
+// been revoked are omitted), except for the root role in which case all local
+// keys are returned (revoked root keys still need to sign new root metadata so
+// clients can verify the new root.json and update their keys db accordingly).
+func (r *Repo) getSignersInDB(roleName string, db *verify.DB) ([]keys.Signer, error) {
+ signers, err := r.local.GetSigners(roleName)
+ if err != nil {
+ return nil, err
+ }
+
+ if roleName == "root" {
+ sorted := make([]keys.Signer, len(signers))
+ copy(sorted, signers)
+ sort.Sort(signer.ByIDs(sorted))
+ return sorted, nil
+ }
+
+ role := db.GetRole(roleName)
+ if role == nil {
+ return nil, nil
+ }
+ if len(role.KeyIDs) == 0 {
+ return nil, nil
+ }
+
+ signersInDB := []keys.Signer{}
+ for _, s := range signers {
+ for _, id := range s.PublicData().IDs() {
+ if _, ok := role.KeyIDs[id]; ok {
+ signersInDB = append(signersInDB, s)
+ }
+ }
+ }
+
+ sort.Sort(signer.ByIDs(signersInDB))
+
+ return signersInDB, nil
+}
+
+// Used to retrieve the signable portion of the metadata when using an external signing tool.
+func (r *Repo) SignedMeta(roleFilename string) (*data.Signed, error) {
+ b, ok := r.meta[roleFilename]
+ if !ok {
+ return nil, ErrMissingMetadata{roleFilename}
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(b, s); err != nil {
+ return nil, err
+ }
+ return s, nil
+}
+
+// delegatorDBs returns a list of key DBs for all incoming delegations.
+func (r *Repo) delegatorDBs(delegateeRole string) ([]*verify.DB, error) {
+ delegatorDBs := []*verify.DB{}
+ for metaName := range r.meta {
+ if roles.IsTopLevelManifest(metaName) && metaName != "targets.json" {
+ continue
+ }
+ roleName := strings.TrimSuffix(metaName, ".json")
+
+ t, err := r.targets(roleName)
+ if err != nil {
+ return nil, err
+ }
+
+ if t.Delegations == nil {
+ continue
+ }
+
+ delegatesToRole := false
+ for _, d := range t.Delegations.Roles {
+ if d.Name == delegateeRole {
+ delegatesToRole = true
+ break
+ }
+ }
+ if !delegatesToRole {
+ continue
+ }
+
+ db, err := verify.NewDBFromDelegations(t.Delegations)
+ if err != nil {
+ return nil, err
+ }
+
+ delegatorDBs = append(delegatorDBs, db)
+ }
+
+ return delegatorDBs, nil
+}
+
+// targetDelegationForPath finds the targets metadata for the role that should
+// sign the given path. The final delegation that led to the returned target
+// metadata is also returned.
+//
+// Since there may be multiple targets roles that are able to sign a specific
+// path, we must choose which roles's metadata to return. If preferredRole is
+// specified (non-empty string) and eligible to sign the given path by way of
+// some delegation chain, targets metadata for that role is returned. If
+// preferredRole is not specified (""), we return targets metadata for the
+// final role visited in the depth-first delegation traversal.
+func (r *Repo) targetDelegationForPath(path string, preferredRole string) (*data.Targets, *targets.Delegation, error) {
+ topLevelKeysDB, err := r.topLevelKeysDB()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ iterator, err := targets.NewDelegationsIterator(path, topLevelKeysDB)
+ if err != nil {
+ return nil, nil, err
+ }
+ d, ok := iterator.Next()
+ if !ok {
+ return nil, nil, ErrNoDelegatedTarget{Path: path}
+ }
+
+ for i := 0; i < defaultMaxDelegations; i++ {
+ targetsMeta, err := r.targets(d.Delegatee.Name)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if preferredRole != "" && d.Delegatee.Name == preferredRole {
+ // The preferredRole is eligible to sign for the given path, and we've
+ // found its metadata. Return it.
+ return targetsMeta, &d, nil
+ }
+
+ if targetsMeta.Delegations != nil && len(targetsMeta.Delegations.Roles) > 0 {
+ db, err := verify.NewDBFromDelegations(targetsMeta.Delegations)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Add delegations to the iterator that are eligible to sign for the
+ // given path (there may be none).
+ iterator.Add(targetsMeta.Delegations.Roles, d.Delegatee.Name, db)
+ }
+
+ next, ok := iterator.Next()
+ if !ok { // No more roles to traverse.
+ if preferredRole == "" {
+ // No preferredRole was given, so return metadata for the final role in the traversal.
+ return targetsMeta, &d, nil
+ } else {
+ // There are no more roles to traverse, so preferredRole is either an
+ // invalid role, or not eligible to sign the given path.
+ return nil, nil, ErrNoDelegatedTarget{Path: path}
+ }
+ }
+
+ d = next
+ }
+
+ return nil, nil, ErrNoDelegatedTarget{Path: path}
+}
+
+func (r *Repo) AddTarget(path string, custom json.RawMessage) error {
+ return r.AddTargets([]string{path}, custom)
+}
+
+func (r *Repo) AddTargetToPreferredRole(path string, custom json.RawMessage, preferredRole string) error {
+ return r.AddTargetsToPreferredRole([]string{path}, custom, preferredRole)
+}
+
+func (r *Repo) AddTargets(paths []string, custom json.RawMessage) error {
+ return r.AddTargetsToPreferredRole(paths, custom, "")
+}
+
+func (r *Repo) AddTargetsToPreferredRole(paths []string, custom json.RawMessage, preferredRole string) error {
+ return r.AddTargetsWithExpiresToPreferredRole(paths, custom, data.DefaultExpires("targets"), preferredRole)
+}
+
+func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int64, path string, custom json.RawMessage) error {
+ // TODO: Rename this to AddTargetWithDigest
+ // https://github.com/theupdateframework/go-tuf/issues/242
+
+ expires := data.DefaultExpires("targets")
+ path = util.NormalizeTarget(path)
+
+ targetsMeta, delegation, err := r.targetDelegationForPath(path, "")
+ if err != nil {
+ return err
+ }
+ // This is the targets role that needs to sign the target file.
+ targetsRoleName := delegation.Delegatee.Name
+
+ meta := data.TargetFileMeta{FileMeta: data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)}}
+ meta.Hashes[digestAlg], err = hex.DecodeString(digest)
+ if err != nil {
+ return err
+ }
+
+ // If custom is provided, set custom, otherwise maintain existing custom
+ // metadata
+ if len(custom) > 0 {
+ meta.Custom = &custom
+ } else if t, ok := targetsMeta.Targets[path]; ok {
+ meta.Custom = t.Custom
+ }
+
+ // What does G2 mean? Copying and pasting this comment from elsewhere in this file.
+ // G2 -> we no longer desire any readers to ever observe non-prefix targets.
+ delete(targetsMeta.Targets, "/"+path)
+ targetsMeta.Targets[path] = meta
+
+ targetsMeta.Expires = expires.Round(time.Second)
+
+ manifestName := targetsRoleName + ".json"
+ if !r.local.FileIsStaged(manifestName) {
+ targetsMeta.Version++
+ }
+
+ err = r.setMeta(manifestName, targetsMeta)
+ if err != nil {
+ return fmt.Errorf("error setting metadata for %q: %w", manifestName, err)
+ }
+
+ return nil
+}
+
+func (r *Repo) AddTargetWithExpires(path string, custom json.RawMessage, expires time.Time) error {
+ return r.AddTargetsWithExpires([]string{path}, custom, expires)
+}
+
+func (r *Repo) AddTargetsWithExpires(paths []string, custom json.RawMessage, expires time.Time) error {
+ return r.AddTargetsWithExpiresToPreferredRole(paths, custom, expires, "")
+}
+
+func (r *Repo) AddTargetWithExpiresToPreferredRole(path string, custom json.RawMessage, expires time.Time, preferredRole string) error {
+ return r.AddTargetsWithExpiresToPreferredRole([]string{path}, custom, expires, preferredRole)
+}
+
+// AddTargetsWithExpiresToPreferredRole signs the staged targets at `paths`.
+//
+// If preferredRole is not the empty string, the target is added to the given
+// role's manifest if delegations allow it. If delegations do not allow the
+// preferredRole to sign the given path, an error is returned.
+func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json.RawMessage, expires time.Time, preferredRole string) error {
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ normalizedPaths := make([]string, len(paths))
+ for i, path := range paths {
+ normalizedPaths[i] = util.NormalizeTarget(path)
+ }
+
+ // As we iterate through staged targets files, we accumulate changes to their
+ // corresponding targets metadata.
+ updatedTargetsMeta := map[string]*data.Targets{}
+
+ if err := r.local.WalkStagedTargets(normalizedPaths, func(path string, target io.Reader) (err error) {
+ originalMeta, delegation, err := r.targetDelegationForPath(path, preferredRole)
+ if err != nil {
+ return err
+ }
+
+ // This is the targets role that needs to sign the target file.
+ targetsRoleName := delegation.Delegatee.Name
+
+ targetsMeta := originalMeta
+ if tm, ok := updatedTargetsMeta[targetsRoleName]; ok {
+ // Metadata in updatedTargetsMeta overrides staged/commited metadata.
+ targetsMeta = tm
+ }
+
+ fileMeta, err := util.GenerateTargetFileMeta(target, r.hashAlgorithms...)
+ if err != nil {
+ return err
+ }
+
+ // If we have custom metadata, set it, otherwise maintain
+ // existing metadata if present
+ if len(custom) > 0 {
+ fileMeta.Custom = &custom
+ } else if tf, ok := targetsMeta.Targets[path]; ok {
+ fileMeta.Custom = tf.Custom
+ }
+
+ // G2 -> we no longer desire any readers to ever observe non-prefix targets.
+ delete(targetsMeta.Targets, "/"+path)
+ targetsMeta.Targets[path] = fileMeta
+
+ updatedTargetsMeta[targetsRoleName] = targetsMeta
+
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ if len(updatedTargetsMeta) == 0 {
+ // This is potentially unexpected behavior kept for backwards compatibility.
+ // See https://github.com/theupdateframework/go-tuf/issues/243
+ t, err := r.topLevelTargets()
+ if err != nil {
+ return err
+ }
+
+ updatedTargetsMeta["targets"] = t
+ }
+
+ exp := expires.Round(time.Second)
+ for roleName, targetsMeta := range updatedTargetsMeta {
+ targetsMeta.Expires = exp
+
+ manifestName := roleName + ".json"
+ if !r.local.FileIsStaged(manifestName) {
+ targetsMeta.Version++
+ }
+
+ err := r.setMeta(manifestName, targetsMeta)
+ if err != nil {
+ return fmt.Errorf("error setting metadata for %q: %w", manifestName, err)
+ }
+ }
+
+ return nil
+}
+
+func (r *Repo) RemoveTarget(path string) error {
+ return r.RemoveTargets([]string{path})
+}
+
+func (r *Repo) RemoveTargets(paths []string) error {
+ return r.RemoveTargetsWithExpires(paths, data.DefaultExpires("targets"))
+}
+
+func (r *Repo) RemoveTargetWithExpires(path string, expires time.Time) error {
+ return r.RemoveTargetsWithExpires([]string{path}, expires)
+}
+
+// If paths is empty, all targets will be removed.
+func (r *Repo) RemoveTargetsWithExpires(paths []string, expires time.Time) error {
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ for metaName := range r.meta {
+ if metaName != "targets.json" && !roles.IsDelegatedTargetsManifest(metaName) {
+ continue
+ }
+
+ err := r.removeTargetsWithExpiresFromMeta(metaName, paths, expires)
+ if err != nil {
+ return fmt.Errorf("could not remove %v from %v: %w", paths, metaName, err)
+ }
+ }
+
+ return nil
+}
+
+func (r *Repo) removeTargetsWithExpiresFromMeta(metaName string, paths []string, expires time.Time) error {
+ roleName := strings.TrimSuffix(metaName, ".json")
+ t, err := r.targets(roleName)
+ if err != nil {
+ return err
+ }
+ removed_targets := []string{}
+ if len(paths) == 0 {
+ for rt := range t.Targets {
+ removed_targets = append(removed_targets, rt)
+ }
+ t.Targets = make(data.TargetFiles)
+ } else {
+ removed := false
+ for _, path := range paths {
+ path = util.NormalizeTarget(path)
+ if _, ok := t.Targets[path]; !ok {
+ r.logger.Printf("[%v] The following target is not present: %v\n", metaName, path)
+ continue
+ }
+ removed = true
+ // G2 -> we no longer desire any readers to ever observe non-prefix targets.
+ delete(t.Targets, "/"+path)
+ delete(t.Targets, path)
+ removed_targets = append(removed_targets, path)
+ }
+ if !removed {
+ return nil
+ }
+ }
+ t.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged(metaName) {
+ t.Version++
+ }
+
+ err = r.setMeta(metaName, t)
+ if err == nil {
+ r.logger.Printf("[%v] Removed targets:\n", metaName)
+ for _, v := range removed_targets {
+ r.logger.Println("*", v)
+ }
+ if len(t.Targets) != 0 {
+ r.logger.Printf("[%v] Added/staged targets:\n", metaName)
+ for k := range t.Targets {
+ r.logger.Println("*", k)
+ }
+ } else {
+ r.logger.Printf("[%v] There are no added/staged targets\n", metaName)
+ }
+ }
+ return err
+}
+
+func (r *Repo) Snapshot() error {
+ return r.SnapshotWithExpires(data.DefaultExpires("snapshot"))
+}
+
+func (r *Repo) snapshotMetadata() []string {
+ ret := []string{"targets.json"}
+
+ for name := range r.meta {
+ if !roles.IsVersionedManifest(name) &&
+ roles.IsDelegatedTargetsManifest(name) {
+ ret = append(ret, name)
+ }
+ }
+
+ return ret
+}
+
+func (r *Repo) SnapshotWithExpires(expires time.Time) error {
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ snapshot, err := r.snapshot()
+ if err != nil {
+ return err
+ }
+
+ // Verify root metadata before verifying signatures on role metadata.
+ if err := r.verifySignatures("root.json"); err != nil {
+ return err
+ }
+
+ for _, metaName := range r.snapshotMetadata() {
+ if err := r.verifySignatures(metaName); err != nil {
+ return err
+ }
+ var err error
+ snapshot.Meta[metaName], err = r.snapshotFileMeta(metaName)
+ if err != nil {
+ return err
+ }
+ }
+ snapshot.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged("snapshot.json") {
+ snapshot.Version++
+ }
+ err = r.setMeta("snapshot.json", snapshot)
+ if err == nil {
+ r.logger.Println("Staged snapshot.json metadata with expiration date:", snapshot.Expires)
+ }
+ return err
+}
+
+func (r *Repo) Timestamp() error {
+ return r.TimestampWithExpires(data.DefaultExpires("timestamp"))
+}
+
+func (r *Repo) TimestampWithExpires(expires time.Time) error {
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ if err := r.verifySignatures("snapshot.json"); err != nil {
+ return err
+ }
+ timestamp, err := r.timestamp()
+ if err != nil {
+ return err
+ }
+ timestamp.Meta["snapshot.json"], err = r.timestampFileMeta("snapshot.json")
+ if err != nil {
+ return err
+ }
+ timestamp.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged("timestamp.json") {
+ timestamp.Version++
+ }
+
+ err = r.setMeta("timestamp.json", timestamp)
+ if err == nil {
+ r.logger.Println("Staged timestamp.json metadata with expiration date:", timestamp.Expires)
+ }
+ return err
+}
+
+func (r *Repo) fileVersions() (map[string]int64, error) {
+ versions := make(map[string]int64)
+
+ for fileName := range r.meta {
+ if roles.IsVersionedManifest(fileName) {
+ continue
+ }
+
+ roleName := strings.TrimSuffix(fileName, ".json")
+
+ var version int64
+
+ switch roleName {
+ case "root":
+ root, err := r.root()
+ if err != nil {
+ return nil, err
+ }
+ version = root.Version
+ case "snapshot":
+ snapshot, err := r.snapshot()
+ if err != nil {
+ return nil, err
+ }
+ version = snapshot.Version
+ case "timestamp":
+ continue
+ default:
+ // Targets or delegated targets manifest.
+ targets, err := r.targets(roleName)
+ if err != nil {
+ return nil, err
+ }
+
+ version = targets.Version
+ }
+
+ versions[fileName] = version
+ }
+
+ return versions, nil
+}
+
+func (r *Repo) fileHashes() (map[string]data.Hashes, error) {
+ hashes := make(map[string]data.Hashes)
+
+ for fileName := range r.meta {
+ if roles.IsVersionedManifest(fileName) {
+ continue
+ }
+
+ roleName := strings.TrimSuffix(fileName, ".json")
+
+ switch roleName {
+ case "snapshot":
+ timestamp, err := r.timestamp()
+ if err != nil {
+ return nil, err
+ }
+
+ if m, ok := timestamp.Meta[fileName]; ok {
+ hashes[fileName] = m.Hashes
+ }
+ case "timestamp":
+ continue
+ default:
+ snapshot, err := r.snapshot()
+ if err != nil {
+ return nil, err
+ }
+ if m, ok := snapshot.Meta[fileName]; ok {
+ hashes[fileName] = m.Hashes
+ }
+
+ if roleName != "root" {
+ // Scalability issue: Commit/fileHashes loads all targets metadata into memory
+ // https://github.com/theupdateframework/go-tuf/issues/245
+ t, err := r.targets(roleName)
+ if err != nil {
+ return nil, err
+ }
+ for name, m := range t.Targets {
+ hashes[path.Join("targets", name)] = m.Hashes
+ }
+ }
+
+ }
+
+ }
+
+ return hashes, nil
+}
+
+func (r *Repo) Commit() error {
+ // check we have all the metadata
+ for _, name := range topLevelMetadata {
+ if _, ok := r.meta[name]; !ok {
+ return ErrMissingMetadata{name}
+ }
+ }
+
+ // check roles are valid
+ root, err := r.root()
+ if err != nil {
+ return err
+ }
+ for name, role := range root.Roles {
+ if len(role.KeyIDs) < role.Threshold {
+ return ErrNotEnoughKeys{name, len(role.KeyIDs), role.Threshold}
+ }
+ }
+
+ // verify hashes in snapshot.json are up to date
+ snapshot, err := r.snapshot()
+ if err != nil {
+ return err
+ }
+ for _, name := range r.snapshotMetadata() {
+ expected, ok := snapshot.Meta[name]
+ if !ok {
+ return fmt.Errorf("tuf: snapshot.json missing hash for %s", name)
+ }
+ actual, err := r.snapshotFileMeta(name)
+ if err != nil {
+ return err
+ }
+ if err := util.SnapshotFileMetaEqual(actual, expected); err != nil {
+ return fmt.Errorf("tuf: invalid %s in snapshot.json: %s", name, err)
+ }
+ }
+
+ // verify hashes in timestamp.json are up to date
+ timestamp, err := r.timestamp()
+ if err != nil {
+ return err
+ }
+ snapshotMeta, err := r.timestampFileMeta("snapshot.json")
+ if err != nil {
+ return err
+ }
+ if err := util.TimestampFileMetaEqual(snapshotMeta, timestamp.Meta["snapshot.json"]); err != nil {
+ return fmt.Errorf("tuf: invalid snapshot.json in timestamp.json: %s", err)
+ }
+
+ for _, name := range topLevelMetadata {
+ if err := r.verifySignatures(name); err != nil {
+ return err
+ }
+ }
+
+ versions, err := r.fileVersions()
+ if err != nil {
+ return err
+ }
+ hashes, err := r.fileHashes()
+ if err != nil {
+ return err
+ }
+
+ err = r.local.Commit(root.ConsistentSnapshot, versions, hashes)
+ if err == nil {
+ r.logger.Println("Committed successfully")
+ }
+ return err
+}
+
+func (r *Repo) Clean() error {
+ err := r.local.Clean()
+ if err == nil {
+ r.logger.Println("Removed all staged metadata and target files")
+ }
+ return err
+}
+
+func (r *Repo) verifySignatures(metaFilename string) error {
+ s, err := r.SignedMeta(metaFilename)
+ if err != nil {
+ return err
+ }
+
+ role := strings.TrimSuffix(metaFilename, ".json")
+
+ dbs, err := r.dbsForRole(role)
+ if err != nil {
+ return err
+ }
+
+ for _, db := range dbs {
+ if err := db.Verify(s, role, 0); err != nil {
+ return ErrInsufficientSignatures{metaFilename, err}
+ }
+ }
+
+ return nil
+}
+
+func (r *Repo) snapshotFileMeta(roleFilename string) (data.SnapshotFileMeta, error) {
+ b, ok := r.meta[roleFilename]
+ if !ok {
+ return data.SnapshotFileMeta{}, ErrMissingMetadata{roleFilename}
+ }
+ return util.GenerateSnapshotFileMeta(bytes.NewReader(b), r.hashAlgorithms...)
+}
+
+func (r *Repo) timestampFileMeta(roleFilename string) (data.TimestampFileMeta, error) {
+ b, ok := r.meta[roleFilename]
+ if !ok {
+ return data.TimestampFileMeta{}, ErrMissingMetadata{roleFilename}
+ }
+ return util.GenerateTimestampFileMeta(bytes.NewReader(b), r.hashAlgorithms...)
+}
+
+func (r *Repo) Payload(roleFilename string) ([]byte, error) {
+ s, err := r.SignedMeta(roleFilename)
+ if err != nil {
+ return nil, err
+ }
+
+ p, err := cjson.EncodeCanonical(s.Signed)
+ if err != nil {
+ return nil, err
+ }
+
+ return p, nil
+}
+
+func (r *Repo) CheckRoleUnexpired(role string, validAt time.Time) error {
+ var expires time.Time
+ switch role {
+ case "root":
+ root, err := r.root()
+ if err != nil {
+ return err
+ }
+ expires = root.Expires
+ case "snapshot":
+ snapshot, err := r.snapshot()
+ if err != nil {
+ return err
+ }
+ expires = snapshot.Expires
+ case "timestamp":
+ timestamp, err := r.timestamp()
+ if err != nil {
+ return err
+ }
+ expires = timestamp.Expires
+ case "targets":
+ targets, err := r.topLevelTargets()
+ if err != nil {
+ return err
+ }
+ expires = targets.Expires
+ default:
+ return fmt.Errorf("invalid role: %s", role)
+ }
+ if expires.Before(validAt) || expires.Equal(validAt) {
+ return fmt.Errorf("role expired on: %s", expires)
+ }
+ return nil
+}
+
+// GetMeta returns the underlying meta file map from the store.
+func (r *Repo) GetMeta() (map[string]json.RawMessage, error) {
+ return r.local.GetMeta()
+}
diff --git a/repo_test.go b/repo_test.go
new file mode 100644
index 0000000..01ae341
--- /dev/null
+++ b/repo_test.go
@@ -0,0 +1,2752 @@
+package tuf
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/elliptic"
+ "crypto/rand"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+ "github.com/secure-systems-lab/go-securesystemslib/encrypted"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/internal/sets"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+ "github.com/theupdateframework/go-tuf/pkg/targets"
+ "github.com/theupdateframework/go-tuf/util"
+ "github.com/theupdateframework/go-tuf/verify"
+ "golang.org/x/crypto/ed25519"
+ . "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type RepoSuite struct{}
+
+var _ = Suite(&RepoSuite{})
+
+func (RepoSuite) TestNewRepo(c *C) {
+ testNewRepo(c, NewRepo)
+}
+
+func (RepoSuite) TestNewRepoIndent(c *C) {
+ testNewRepo(c, func(local LocalStore, hashAlgorithms ...string) (*Repo, error) {
+ return NewRepoIndent(local, "", "\t")
+ })
+}
+
+// UniqueKeys returns the unique keys for each associated role.
+// We might have multiple key IDs that correspond to the same key.
+func UniqueKeys(r *data.Root) map[string][]*data.PublicKey {
+ keysByRole := make(map[string][]*data.PublicKey)
+ for name, role := range r.Roles {
+ seen := make(map[string]struct{})
+ roleKeys := []*data.PublicKey{}
+ for _, id := range role.KeyIDs {
+ // Double-check that there is actually a key with that ID.
+ if key, ok := r.Keys[id]; ok {
+ verifier, err := keys.GetVerifier(key)
+ if err != nil {
+ continue
+ }
+ val := verifier.Public()
+ if _, ok := seen[val]; ok {
+ continue
+ }
+ seen[val] = struct{}{}
+ roleKeys = append(roleKeys, key)
+ }
+ }
+ keysByRole[name] = roleKeys
+ }
+ return keysByRole
+}
+
+// AssertNumUniqueKeys verifies that the number of unique root keys for a given role is as expected.
+func (*RepoSuite) assertNumUniqueKeys(c *C, root *data.Root, role string, num int) {
+ c.Assert(UniqueKeys(root)[role], HasLen, num)
+}
+
+func testNewRepo(c *C, newRepo func(local LocalStore, hashAlgorithms ...string) (*Repo, error)) {
+ meta := map[string]json.RawMessage{
+ "root.json": []byte(`{
+ "signed": {
+ "_type": "root",
+ "version": 1,
+ "expires": "2015-12-26T03:26:55.821520874Z",
+ "keys": {},
+ "roles": {}
+ },
+ "signatures": []
+ }`),
+ "targets.json": []byte(`{
+ "signed": {
+ "_type": "targets",
+ "version": 1,
+ "expires": "2015-03-26T03:26:55.82155686Z",
+ "targets": {}
+ },
+ "signatures": []
+ }`),
+ "snapshot.json": []byte(`{
+ "signed": {
+ "_type": "snapshot",
+ "version": 1,
+ "expires": "2015-01-02T03:26:55.821585981Z",
+ "meta": {}
+ },
+ "signatures": []
+ }`),
+ "timestamp.json": []byte(`{
+ "signed": {
+ "_type": "timestamp",
+ "version": 1,
+ "expires": "2014-12-27T03:26:55.821599702Z",
+ "meta": {}
+ },
+ "signatures": []
+ }`),
+ }
+ local := MemoryStore(meta, nil)
+ r, err := newRepo(local)
+ c.Assert(err, IsNil)
+
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Type, Equals, "root")
+ c.Assert(root.Version, Equals, int64(1))
+ c.Assert(root.Keys, NotNil)
+ c.Assert(root.Keys, HasLen, 0)
+
+ targets, err := r.topLevelTargets()
+ c.Assert(err, IsNil)
+ c.Assert(targets.Type, Equals, "targets")
+ c.Assert(targets.Version, Equals, int64(1))
+ c.Assert(targets.Targets, NotNil)
+ c.Assert(targets.Targets, HasLen, 0)
+
+ snapshot, err := r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Type, Equals, "snapshot")
+ c.Assert(snapshot.Version, Equals, int64(1))
+ c.Assert(snapshot.Meta, NotNil)
+ c.Assert(snapshot.Meta, HasLen, 0)
+
+ timestamp, err := r.timestamp()
+ c.Assert(err, IsNil)
+ c.Assert(timestamp.Type, Equals, "timestamp")
+ c.Assert(timestamp.Version, Equals, int64(1))
+ c.Assert(timestamp.Meta, NotNil)
+ c.Assert(timestamp.Meta, HasLen, 0)
+}
+
+func (rs *RepoSuite) TestInit(c *C) {
+ local := MemoryStore(
+ make(map[string]json.RawMessage),
+ map[string][]byte{"foo.txt": []byte("foo")},
+ )
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // Init() sets root.ConsistentSnapshot
+ for _, v := range []bool{true, false} {
+ c.Assert(r.Init(v), IsNil)
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.ConsistentSnapshot, Equals, v)
+ }
+
+ // Add a target.
+ generateAndAddPrivateKey(c, r, "targets")
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+
+ // Init() fails if targets have been added
+ c.Assert(r.Init(true), Equals, ErrInitNotAllowed)
+}
+
+func genKey(c *C, r *Repo, role string) []string {
+ keyids, err := r.GenKey(role)
+ c.Assert(err, IsNil)
+ c.Assert(len(keyids) > 0, Equals, true)
+ return keyids
+}
+
+func (rs *RepoSuite) TestGenKey(c *C) {
+ local := MemoryStore(make(map[string]json.RawMessage), nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // generate a key for an unknown role
+ _, err = r.GenKey("foo")
+ c.Assert(err, Equals, ErrInvalidRole{"foo", "only support adding keys for top-level roles"})
+
+ // generate a root key
+ ids := genKey(c, r, "root")
+
+ // check root metadata is correct
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Roles, NotNil)
+ c.Assert(root.Roles, HasLen, 1)
+ rs.assertNumUniqueKeys(c, root, "root", 1)
+ rootRole, ok := root.Roles["root"]
+ if !ok {
+ c.Fatal("missing root role")
+ }
+ c.Assert(rootRole.KeyIDs, HasLen, 1)
+ c.Assert(rootRole.KeyIDs, DeepEquals, ids)
+ for _, keyID := range ids {
+ k, ok := root.Keys[keyID]
+ if !ok {
+ c.Fatal("missing key")
+ }
+ c.Assert(k.IDs(), DeepEquals, ids)
+ pk, err := keys.GetVerifier(k)
+ c.Assert(err, IsNil)
+ c.Assert(pk.Public(), HasLen, ed25519.PublicKeySize)
+ }
+
+ // check root key + role are in db
+ db, err := r.topLevelKeysDB()
+ c.Assert(err, IsNil)
+ for _, keyID := range ids {
+ rootKey, err := db.GetVerifier(keyID)
+ c.Assert(err, IsNil)
+ c.Assert(rootKey.MarshalPublicKey().IDs(), DeepEquals, ids)
+ role := db.GetRole("root")
+ c.Assert(role.KeyIDs, DeepEquals, sets.StringSliceToSet(ids))
+
+ // check the key was saved correctly
+ localKeys, err := local.GetSigners("root")
+ c.Assert(err, IsNil)
+ c.Assert(localKeys, HasLen, 1)
+ c.Assert(localKeys[0].PublicData().IDs(), DeepEquals, ids)
+
+ // check RootKeys() is correct
+ rootKeys, err := r.RootKeys()
+ c.Assert(err, IsNil)
+ c.Assert(rootKeys, HasLen, 1)
+ c.Assert(rootKeys[0].IDs(), DeepEquals, rootKey.MarshalPublicKey().IDs())
+ pk, err := keys.GetVerifier(rootKeys[0])
+ c.Assert(err, IsNil)
+ c.Assert(pk.Public(), DeepEquals, rootKey.Public())
+ }
+
+ rootKey, err := db.GetVerifier(ids[0])
+ c.Assert(err, IsNil)
+
+ // generate two targets keys
+ genKey(c, r, "targets")
+ genKey(c, r, "targets")
+
+ // check root metadata is correct
+ root, err = r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Roles, HasLen, 2)
+ rs.assertNumUniqueKeys(c, root, "root", 1)
+ rs.assertNumUniqueKeys(c, root, "targets", 2)
+ targetsRole, ok := root.Roles["targets"]
+ if !ok {
+ c.Fatal("missing targets role")
+ }
+ c.Assert(targetsRole.KeyIDs, HasLen, 2)
+ targetKeyIDs := make(map[string]struct{}, 2)
+ db, err = r.topLevelKeysDB()
+ c.Assert(err, IsNil)
+ for _, id := range targetsRole.KeyIDs {
+ targetKeyIDs[id] = struct{}{}
+ _, ok = root.Keys[id]
+ if !ok {
+ c.Fatal("missing key")
+ }
+ verifier, err := db.GetVerifier(id)
+ c.Assert(err, IsNil)
+ c.Assert(verifier.MarshalPublicKey().ContainsID(id), Equals, true)
+ }
+ role := db.GetRole("targets")
+ c.Assert(role.KeyIDs, DeepEquals, targetKeyIDs)
+
+ // check RootKeys() is unchanged
+ rootKeys, err := r.RootKeys()
+ c.Assert(err, IsNil)
+ c.Assert(rootKeys, HasLen, 1)
+ c.Assert(rootKeys[0].IDs(), DeepEquals, rootKey.MarshalPublicKey().IDs())
+
+ // check the keys were saved correctly
+ localKeys, err := local.GetSigners("targets")
+ c.Assert(err, IsNil)
+ c.Assert(localKeys, HasLen, 2)
+ for _, key := range localKeys {
+ found := false
+ for _, id := range targetsRole.KeyIDs {
+ if key.PublicData().ContainsID(id) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ c.Fatal("missing key")
+ }
+ }
+
+ // check root.json got staged
+ meta, err := local.GetMeta()
+ c.Assert(err, IsNil)
+ rootJSON, ok := meta["root.json"]
+ if !ok {
+ c.Fatal("missing root metadata")
+ }
+ s := &data.Signed{}
+ c.Assert(json.Unmarshal(rootJSON, s), IsNil)
+ stagedRoot := &data.Root{}
+ c.Assert(json.Unmarshal(s.Signed, stagedRoot), IsNil)
+ c.Assert(stagedRoot.Type, Equals, root.Type)
+ c.Assert(stagedRoot.Version, Equals, root.Version)
+ c.Assert(stagedRoot.Expires.UnixNano(), Equals, root.Expires.UnixNano())
+
+ // make sure both root and stagedRoot have evaluated IDs(), otherwise
+ // DeepEquals will fail because those values might not have been
+ // computed yet.
+ for _, key := range root.Keys {
+ key.IDs()
+ }
+ for _, key := range stagedRoot.Keys {
+ key.IDs()
+ }
+ c.Assert(stagedRoot.Keys, DeepEquals, root.Keys)
+ c.Assert(stagedRoot.Roles, DeepEquals, root.Roles)
+}
+
+func addPrivateKey(c *C, r *Repo, role string, key keys.Signer) []string {
+ err := r.AddPrivateKey(role, key)
+ c.Assert(err, IsNil)
+ keyids := key.PublicData().IDs()
+ c.Assert(len(keyids) > 0, Equals, true)
+ return keyids
+}
+
+func generateAndAddPrivateKey(c *C, r *Repo, role string) []string {
+ signer, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ return addPrivateKey(c, r, role, signer)
+}
+
+func (rs *RepoSuite) TestAddPrivateKey(c *C) {
+ local := MemoryStore(make(map[string]json.RawMessage), nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // generate a key for an unknown role
+ signer, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ err = r.AddPrivateKey("foo", signer)
+ c.Assert(err, Equals, ErrInvalidRole{"foo", "only support adding keys for top-level roles"})
+
+ // add a root key
+ ids := addPrivateKey(c, r, "root", signer)
+
+ // check root metadata is correct
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Version, Equals, int64(1))
+ c.Assert(root.Roles, NotNil)
+ c.Assert(root.Roles, HasLen, 1)
+ rs.assertNumUniqueKeys(c, root, "root", 1)
+ rootRole, ok := root.Roles["root"]
+ if !ok {
+ c.Fatal("missing root role")
+ }
+ c.Assert(rootRole.KeyIDs, HasLen, 1)
+ c.Assert(rootRole.KeyIDs, DeepEquals, ids)
+ for _, keyID := range ids {
+ k, ok := root.Keys[keyID]
+ if !ok {
+ c.Fatalf("missing key %s", keyID)
+ }
+ c.Assert(k.IDs(), DeepEquals, ids)
+ pk, err := keys.GetVerifier(k)
+ c.Assert(err, IsNil)
+ c.Assert(pk.Public(), HasLen, ed25519.PublicKeySize)
+ }
+
+ // check root key + role are in db
+ db, err := r.topLevelKeysDB()
+ c.Assert(err, IsNil)
+ for _, keyID := range ids {
+ rootKey, err := db.GetVerifier(keyID)
+ c.Assert(err, IsNil)
+ c.Assert(rootKey.MarshalPublicKey().IDs(), DeepEquals, ids)
+ role := db.GetRole("root")
+ c.Assert(role.KeyIDs, DeepEquals, sets.StringSliceToSet(ids))
+
+ // check the key was saved correctly
+ localKeys, err := local.GetSigners("root")
+ c.Assert(err, IsNil)
+ c.Assert(localKeys, HasLen, 1)
+ c.Assert(localKeys[0].PublicData().IDs(), DeepEquals, ids)
+
+ // check RootKeys() is correct
+ rootKeys, err := r.RootKeys()
+ c.Assert(err, IsNil)
+ c.Assert(rootKeys, HasLen, 1)
+ c.Assert(rootKeys[0].IDs(), DeepEquals, rootKey.MarshalPublicKey().IDs())
+ pk, err := keys.GetVerifier(rootKeys[0])
+ c.Assert(err, IsNil)
+ c.Assert(pk.Public(), DeepEquals, rootKey.Public())
+ }
+
+ rootKey, err := db.GetVerifier(ids[0])
+ c.Assert(err, IsNil)
+
+ // generate two targets keys
+ generateAndAddPrivateKey(c, r, "targets")
+ generateAndAddPrivateKey(c, r, "targets")
+
+ // check root metadata is correct
+ root, err = r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Roles, HasLen, 2)
+ rs.assertNumUniqueKeys(c, root, "root", 1)
+ rs.assertNumUniqueKeys(c, root, "targets", 2)
+ targetsRole, ok := root.Roles["targets"]
+ if !ok {
+ c.Fatal("missing targets role")
+ }
+ c.Assert(targetsRole.KeyIDs, HasLen, 2)
+ targetKeyIDs := make(map[string]struct{}, 2)
+ db, err = r.topLevelKeysDB()
+ c.Assert(err, IsNil)
+ for _, id := range targetsRole.KeyIDs {
+ targetKeyIDs[id] = struct{}{}
+ _, ok = root.Keys[id]
+ if !ok {
+ c.Fatal("missing key")
+ }
+ verifier, err := db.GetVerifier(id)
+ c.Assert(err, IsNil)
+ c.Assert(verifier.MarshalPublicKey().ContainsID(id), Equals, true)
+ }
+ role := db.GetRole("targets")
+ c.Assert(role.KeyIDs, DeepEquals, targetKeyIDs)
+
+ // check RootKeys() is unchanged
+ rootKeys, err := r.RootKeys()
+ c.Assert(err, IsNil)
+ c.Assert(rootKeys, HasLen, 1)
+ c.Assert(rootKeys[0].IDs(), DeepEquals, rootKey.MarshalPublicKey().IDs())
+
+ // check the keys were saved correctly
+ localKeys, err := local.GetSigners("targets")
+ c.Assert(err, IsNil)
+ c.Assert(localKeys, HasLen, 2)
+ for _, key := range localKeys {
+ found := false
+ for _, id := range targetsRole.KeyIDs {
+ if key.PublicData().ContainsID(id) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ c.Fatal("missing key")
+ }
+ }
+
+ // check root.json got staged
+ meta, err := local.GetMeta()
+ c.Assert(err, IsNil)
+ rootJSON, ok := meta["root.json"]
+ if !ok {
+ c.Fatal("missing root metadata")
+ }
+ s := &data.Signed{}
+ c.Assert(json.Unmarshal(rootJSON, s), IsNil)
+ stagedRoot := &data.Root{}
+ c.Assert(json.Unmarshal(s.Signed, stagedRoot), IsNil)
+ c.Assert(stagedRoot.Type, Equals, root.Type)
+ c.Assert(stagedRoot.Version, Equals, root.Version)
+ c.Assert(stagedRoot.Expires.UnixNano(), Equals, root.Expires.UnixNano())
+
+ // make sure both root and stagedRoot have evaluated IDs(), otherwise
+ // DeepEquals will fail because those values might not have been
+ // computed yet.
+ for _, key := range root.Keys {
+ key.IDs()
+ }
+ for _, key := range stagedRoot.Keys {
+ key.IDs()
+ }
+ c.Assert(stagedRoot.Keys, DeepEquals, root.Keys)
+ c.Assert(stagedRoot.Roles, DeepEquals, root.Roles)
+
+ // commit to make sure we don't modify metadata after committing metadata.
+ generateAndAddPrivateKey(c, r, "snapshot")
+ generateAndAddPrivateKey(c, r, "timestamp")
+ c.Assert(r.AddTargets([]string{}, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ // add the same root key to make sure the metadata is unmodified.
+ oldRoot, err := r.root()
+ c.Assert(err, IsNil)
+ addPrivateKey(c, r, "root", signer)
+ newRoot, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(oldRoot, DeepEquals, newRoot)
+ if r.local.FileIsStaged("root.json") {
+ c.Fatal("root should not be marked dirty")
+ }
+}
+
+func (rs *RepoSuite) TestRevokeKey(c *C) {
+ local := MemoryStore(make(map[string]json.RawMessage), nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // revoking a key for an unknown role returns ErrInvalidRole
+ c.Assert(r.RevokeKey("foo", ""), DeepEquals, ErrInvalidRole{"foo", "only revocations for top-level roles supported"})
+
+ // revoking a key which doesn't exist returns ErrKeyNotFound
+ c.Assert(r.RevokeKey("root", "nonexistent"), DeepEquals, ErrKeyNotFound{"root", "nonexistent"})
+
+ // generate keys
+ genKey(c, r, "root")
+ target1IDs := genKey(c, r, "targets")
+ target2IDs := genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Roles, NotNil)
+ c.Assert(root.Roles, HasLen, 4)
+ c.Assert(root.Keys, NotNil)
+ rs.assertNumUniqueKeys(c, root, "root", 1)
+ rs.assertNumUniqueKeys(c, root, "targets", 2)
+ rs.assertNumUniqueKeys(c, root, "snapshot", 1)
+ rs.assertNumUniqueKeys(c, root, "timestamp", 1)
+
+ // revoke a key
+ targetsRole, ok := root.Roles["targets"]
+ if !ok {
+ c.Fatal("missing targets role")
+ }
+ c.Assert(targetsRole.KeyIDs, HasLen, len(target1IDs)+len(target2IDs))
+ id := targetsRole.KeyIDs[0]
+ c.Assert(r.RevokeKey("targets", id), IsNil)
+
+ // make sure all the other key ids were also revoked
+ for _, id := range target1IDs {
+ c.Assert(r.RevokeKey("targets", id), DeepEquals, ErrKeyNotFound{"targets", id})
+ }
+
+ // check root was updated
+ root, err = r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Roles, NotNil)
+ c.Assert(root.Roles, HasLen, 4)
+ c.Assert(root.Keys, NotNil)
+ rs.assertNumUniqueKeys(c, root, "root", 1)
+ rs.assertNumUniqueKeys(c, root, "targets", 1)
+ rs.assertNumUniqueKeys(c, root, "snapshot", 1)
+ rs.assertNumUniqueKeys(c, root, "timestamp", 1)
+ targetsRole, ok = root.Roles["targets"]
+ if !ok {
+ c.Fatal("missing targets role")
+ }
+ c.Assert(targetsRole.KeyIDs, HasLen, 1)
+ c.Assert(targetsRole.KeyIDs, DeepEquals, target2IDs)
+}
+
+func (rs *RepoSuite) TestRevokeKeyInMultipleRoles(c *C) {
+ local := MemoryStore(make(map[string]json.RawMessage), nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // generate keys. add a root key that is shared with the targets role
+ rootSigner, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("root", rootSigner.PublicData()), IsNil)
+ sharedSigner, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ sharedIDs := sharedSigner.PublicData().IDs()
+ c.Assert(r.AddVerificationKey("root", sharedSigner.PublicData()), IsNil)
+ c.Assert(r.AddVerificationKey("targets", sharedSigner.PublicData()), IsNil)
+ targetIDs := genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Roles, NotNil)
+ c.Assert(root.Roles, HasLen, 4)
+ c.Assert(root.Keys, NotNil)
+ rs.assertNumUniqueKeys(c, root, "root", 2)
+ rs.assertNumUniqueKeys(c, root, "targets", 2)
+ rs.assertNumUniqueKeys(c, root, "snapshot", 1)
+ rs.assertNumUniqueKeys(c, root, "timestamp", 1)
+
+ // revoke a key
+ targetsRole, ok := root.Roles["targets"]
+ if !ok {
+ c.Fatal("missing targets role")
+ }
+ c.Assert(targetsRole.KeyIDs, HasLen, len(targetIDs)+len(sharedIDs))
+ id := targetsRole.KeyIDs[0]
+ c.Assert(r.RevokeKey("targets", id), IsNil)
+
+ // make sure all the other key ids were also revoked
+ for _, id := range sharedIDs {
+ c.Assert(r.RevokeKey("targets", id), DeepEquals, ErrKeyNotFound{"targets", id})
+ }
+
+ // check root was updated
+ root, err = r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Roles, NotNil)
+ c.Assert(root.Roles, HasLen, 4)
+ c.Assert(root.Keys, NotNil)
+ // the shared root/targets signer should still be present in root keys
+ c.Assert(UniqueKeys(root)["root"], DeepEquals,
+ []*data.PublicKey{rootSigner.PublicData(), sharedSigner.PublicData()})
+ rs.assertNumUniqueKeys(c, root, "root", 2)
+ rs.assertNumUniqueKeys(c, root, "targets", 1)
+ rs.assertNumUniqueKeys(c, root, "snapshot", 1)
+ rs.assertNumUniqueKeys(c, root, "timestamp", 1)
+ targetsRole, ok = root.Roles["targets"]
+ if !ok {
+ c.Fatal("missing targets role")
+ }
+ c.Assert(targetsRole.KeyIDs, HasLen, 1)
+ c.Assert(targetsRole.KeyIDs, DeepEquals, targetIDs)
+}
+
+func (rs *RepoSuite) TestSign(c *C) {
+ meta := map[string]json.RawMessage{"root.json": []byte(`{"signed":{},"signatures":[]}`)}
+ local := MemoryStore(meta, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ c.Assert(r.Sign("foo.json"), Equals, ErrMissingMetadata{"foo.json"})
+
+ // signing with no keys returns ErrNoKeys
+ c.Assert(r.Sign("root.json"), Equals, ErrNoKeys{"root.json"})
+
+ checkSigIDs := func(keyIDs ...string) {
+ meta, err := local.GetMeta()
+ c.Assert(err, IsNil)
+ rootJSON, ok := meta["root.json"]
+ if !ok {
+ c.Fatal("missing root.json")
+ }
+ s := &data.Signed{}
+ c.Assert(json.Unmarshal(rootJSON, s), IsNil)
+ c.Assert(s.Signatures, HasLen, len(keyIDs))
+
+ // Signatures may be in any order, so must sort key IDs before comparison.
+ wantKeyIDs := append([]string{}, keyIDs...)
+ sort.Strings(wantKeyIDs)
+
+ gotKeyIDs := []string{}
+ for _, sig := range s.Signatures {
+ gotKeyIDs = append(gotKeyIDs, sig.KeyID)
+ }
+ sort.Strings(gotKeyIDs)
+
+ c.Assert(wantKeyIDs, DeepEquals, gotKeyIDs)
+ }
+
+ // signing with an available key generates a signature
+ signer, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(local.SaveSigner("root", signer), IsNil)
+ c.Assert(r.Sign("root.json"), IsNil)
+ checkSigIDs(signer.PublicData().IDs()...)
+
+ // signing again does not generate a duplicate signature
+ c.Assert(r.Sign("root.json"), IsNil)
+ checkSigIDs(signer.PublicData().IDs()...)
+
+ // signing with a new available key generates another signature
+ newKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(local.SaveSigner("root", newKey), IsNil)
+ c.Assert(r.Sign("root.json"), IsNil)
+ checkSigIDs(append(signer.PublicData().IDs(), newKey.PublicData().IDs()...)...)
+
+ // attempt to sign missing metadata
+ c.Assert(r.Sign("targets.json"), Equals, ErrMissingMetadata{"targets.json"})
+}
+
+func (rs *RepoSuite) TestStatus(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+ c.Assert(r.SnapshotWithExpires(time.Now().Add(24*time.Hour)), IsNil)
+ c.Assert(r.TimestampWithExpires(time.Now().Add(1*time.Hour)), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ expires := time.Now().Add(2 * time.Hour)
+ c.Assert(r.CheckRoleUnexpired("timestamp", expires), ErrorMatches, "role expired on.*")
+ c.Assert(r.CheckRoleUnexpired("snapshot", expires), IsNil)
+ c.Assert(r.CheckRoleUnexpired("targets", expires), IsNil)
+ c.Assert(r.CheckRoleUnexpired("root", expires), IsNil)
+}
+
+func (rs *RepoSuite) TestCommit(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo"), "bar.txt": []byte("bar")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // commit without root.json
+ c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"root.json"})
+
+ // Init should create targets.json, but not signed yet
+ r.Init(false)
+ c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"snapshot.json"})
+
+ genKey(c, r, "root")
+
+ // commit without snapshot.json
+ genKey(c, r, "targets")
+ c.Assert(r.Sign("targets.json"), IsNil)
+ c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"snapshot.json"})
+
+ // commit without timestamp.json
+ genKey(c, r, "snapshot")
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"timestamp.json"})
+
+ // commit with timestamp.json but no timestamp key
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), DeepEquals, ErrInsufficientSignatures{"timestamp.json", verify.ErrNoSignatures})
+
+ // commit success
+ genKey(c, r, "timestamp")
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ // commit with an invalid root hash in snapshot.json due to new key creation
+ genKey(c, r, "targets")
+ c.Assert(r.Sign("targets.json"), IsNil)
+ c.Assert(r.Commit(), DeepEquals, errors.New("tuf: invalid targets.json in snapshot.json: wrong length, expected 338 got 552"))
+
+ // commit with an invalid targets hash in snapshot.json
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.AddTarget("bar.txt", nil), IsNil)
+ c.Assert(r.Commit(), DeepEquals, errors.New("tuf: invalid targets.json in snapshot.json: wrong length, expected 552 got 725"))
+
+ // commit with an invalid timestamp
+ c.Assert(r.Snapshot(), IsNil)
+ err = r.Commit()
+ c.Assert(err, NotNil)
+ c.Assert(err.Error()[0:44], Equals, "tuf: invalid snapshot.json in timestamp.json")
+
+ // commit with a role's threshold greater than number of keys
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ role, ok := root.Roles["timestamp"]
+ if !ok {
+ c.Fatal("missing timestamp role")
+ }
+ c.Assert(role.KeyIDs, HasLen, 1)
+ c.Assert(role.Threshold, Equals, 1)
+ c.Assert(r.RevokeKey("timestamp", role.KeyIDs[0]), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), DeepEquals, ErrNotEnoughKeys{"timestamp", 0, 1})
+}
+
+func (rs *RepoSuite) TestCommitVersions(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ // on initial commit everything should be at version 1.
+ rootVersion, err := r.RootVersion()
+ c.Assert(err, IsNil)
+ c.Assert(rootVersion, Equals, int64(1))
+
+ targetsVersion, err := r.TargetsVersion()
+ c.Assert(err, IsNil)
+ c.Assert(targetsVersion, Equals, int64(1))
+
+ snapshotVersion, err := r.SnapshotVersion()
+ c.Assert(err, IsNil)
+ c.Assert(snapshotVersion, Equals, int64(1))
+
+ timestampVersion, err := r.SnapshotVersion()
+ c.Assert(err, IsNil)
+ c.Assert(timestampVersion, Equals, int64(1))
+
+ // taking a snapshot should only increment snapshot and timestamp.
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ rootVersion, err = r.RootVersion()
+ c.Assert(err, IsNil)
+ c.Assert(rootVersion, Equals, int64(1))
+
+ targetsVersion, err = r.TargetsVersion()
+ c.Assert(err, IsNil)
+ c.Assert(targetsVersion, Equals, int64(1))
+
+ snapshotVersion, err = r.SnapshotVersion()
+ c.Assert(err, IsNil)
+ c.Assert(snapshotVersion, Equals, int64(2))
+
+ timestampVersion, err = r.SnapshotVersion()
+ c.Assert(err, IsNil)
+ c.Assert(timestampVersion, Equals, int64(2))
+
+ // rotating multiple keys should increment the root once.
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ rootVersion, err = r.RootVersion()
+ c.Assert(err, IsNil)
+ c.Assert(rootVersion, Equals, int64(2))
+
+ targetsVersion, err = r.TargetsVersion()
+ c.Assert(err, IsNil)
+ c.Assert(targetsVersion, Equals, int64(1))
+
+ snapshotVersion, err = r.SnapshotVersion()
+ c.Assert(err, IsNil)
+ c.Assert(snapshotVersion, Equals, int64(3))
+
+ timestampVersion, err = r.TimestampVersion()
+ c.Assert(err, IsNil)
+ c.Assert(timestampVersion, Equals, int64(3))
+}
+
+type tmpDir struct {
+ path string
+ c *C
+}
+
+func newTmpDir(c *C) *tmpDir {
+ return &tmpDir{path: c.MkDir(), c: c}
+}
+
+func (t *tmpDir) assertExists(path string) {
+ if _, err := os.Stat(filepath.Join(t.path, path)); os.IsNotExist(err) {
+ t.c.Fatalf("expected path to exist but it doesn't: %s", path)
+ }
+}
+
+func (t *tmpDir) assertNotExist(path string) {
+ if _, err := os.Stat(filepath.Join(t.path, path)); !os.IsNotExist(err) {
+ t.c.Fatalf("expected path to not exist but it does: %s", path)
+ }
+}
+
+func (t *tmpDir) assertHashedFilesExist(path string, hashes data.Hashes) {
+ t.c.Assert(len(hashes) > 0, Equals, true)
+ for _, path := range util.HashedPaths(path, hashes) {
+ t.assertExists(path)
+ }
+}
+
+func (t *tmpDir) assertHashedFilesNotExist(path string, hashes data.Hashes) {
+ for _, path := range util.HashedPaths(path, hashes) {
+ t.assertNotExist(path)
+ }
+}
+
+func (t *tmpDir) assertVersionedFileExist(path string, version int64) {
+ t.assertExists(util.VersionedPath(path, version))
+}
+
+func (t *tmpDir) assertVersionedFileNotExist(path string, version int64) {
+ t.assertNotExist(util.VersionedPath(path, version))
+}
+
+func (t *tmpDir) assertEmpty(dir string) {
+ path := filepath.Join(t.path, dir)
+ f, err := os.Stat(path)
+ if os.IsNotExist(err) {
+ t.c.Fatalf("expected dir to exist but it doesn't: %s", dir)
+ }
+ t.c.Assert(err, IsNil)
+ t.c.Assert(f.IsDir(), Equals, true)
+
+ entries, err := os.ReadDir(path)
+ t.c.Assert(err, IsNil)
+ // check that all (if any) entries are also empty
+ for _, e := range entries {
+ t.assertEmpty(filepath.Join(dir, e.Name()))
+ }
+}
+
+func (t *tmpDir) assertFileContent(path, content string) {
+ actual := t.readFile(path)
+ t.c.Assert(string(actual), Equals, content)
+}
+
+func (t *tmpDir) stagedTargetPath(path string) string {
+ return filepath.Join(t.path, "staged", "targets", path)
+}
+
+func (t *tmpDir) writeStagedTarget(path, data string) {
+ path = t.stagedTargetPath(path)
+ t.c.Assert(os.MkdirAll(filepath.Dir(path), 0755), IsNil)
+ t.c.Assert(os.WriteFile(path, []byte(data), 0644), IsNil)
+}
+
+func (t *tmpDir) readFile(path string) []byte {
+ t.assertExists(path)
+ data, err := os.ReadFile(filepath.Join(t.path, path))
+ t.c.Assert(err, IsNil)
+ return data
+}
+
+func (rs *RepoSuite) TestCommitFileSystem(c *C) {
+ tmp := newTmpDir(c)
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // don't use consistent snapshots to make the checks simpler
+ c.Assert(r.Init(false), IsNil)
+
+ // cleaning with nothing staged or committed should fail
+ c.Assert(r.Clean(), Equals, ErrNewRepository)
+
+ // generating keys should stage root.json and create repo dirs
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ tmp.assertExists("staged/root.json")
+ tmp.assertEmpty("repository")
+ tmp.assertEmpty("staged/targets")
+
+ // cleaning with nothing committed should fail
+ c.Assert(r.Clean(), Equals, ErrNewRepository)
+
+ // adding a non-existent file fails
+ c.Assert(r.AddTarget("foo.txt", nil), Equals, ErrFileNotFound{tmp.stagedTargetPath("foo.txt")})
+ tmp.assertEmpty("repository")
+
+ // adding a file stages targets.json
+ tmp.writeStagedTarget("foo.txt", "foo")
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+ tmp.assertExists("staged/targets.json")
+ tmp.assertEmpty("repository")
+ t, err := r.topLevelTargets()
+ c.Assert(err, IsNil)
+ c.Assert(t.Targets, HasLen, 1)
+ if _, ok := t.Targets["foo.txt"]; !ok {
+ c.Fatal("missing target file: foo.txt")
+ }
+
+ // Snapshot() stages snapshot.json
+ c.Assert(r.Snapshot(), IsNil)
+ tmp.assertExists("staged/snapshot.json")
+ tmp.assertEmpty("repository")
+
+ // Timestamp() stages timestamp.json
+ c.Assert(r.Timestamp(), IsNil)
+ tmp.assertExists("staged/timestamp.json")
+ tmp.assertEmpty("repository")
+
+ // committing moves files from staged -> repository
+ c.Assert(r.Commit(), IsNil)
+ tmp.assertExists("repository/root.json")
+ tmp.assertExists("repository/targets.json")
+ tmp.assertExists("repository/snapshot.json")
+ tmp.assertExists("repository/timestamp.json")
+ tmp.assertFileContent("repository/targets/foo.txt", "foo")
+ tmp.assertEmpty("staged/targets")
+ tmp.assertEmpty("staged")
+
+ // adding and committing another file moves it into repository/targets
+ tmp.writeStagedTarget("path/to/bar.txt", "bar")
+ c.Assert(r.AddTarget("path/to/bar.txt", nil), IsNil)
+ tmp.assertExists("staged/targets.json")
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ tmp.assertFileContent("repository/targets/foo.txt", "foo")
+ tmp.assertFileContent("repository/targets/path/to/bar.txt", "bar")
+ tmp.assertEmpty("staged/targets")
+ tmp.assertEmpty("staged")
+
+ // removing and committing a file removes it from repository/targets
+ c.Assert(r.RemoveTarget("foo.txt"), IsNil)
+ tmp.assertExists("staged/targets.json")
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ tmp.assertNotExist("repository/targets/foo.txt")
+ tmp.assertFileContent("repository/targets/path/to/bar.txt", "bar")
+ tmp.assertEmpty("staged/targets")
+ tmp.assertEmpty("staged")
+}
+
+func (rs *RepoSuite) TestCommitFileSystemWithNewRepositories(c *C) {
+ tmp := newTmpDir(c)
+
+ newRepo := func() *Repo {
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+ return r
+ }
+
+ genKey(c, newRepo(), "root")
+ genKey(c, newRepo(), "targets")
+ genKey(c, newRepo(), "snapshot")
+ genKey(c, newRepo(), "timestamp")
+
+ tmp.writeStagedTarget("foo.txt", "foo")
+ c.Assert(newRepo().AddTarget("foo.txt", nil), IsNil)
+ c.Assert(newRepo().Snapshot(), IsNil)
+ c.Assert(newRepo().Timestamp(), IsNil)
+ c.Assert(newRepo().Commit(), IsNil)
+}
+
+func (rs *RepoSuite) TestConsistentSnapshot(c *C) {
+ tmp := newTmpDir(c)
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local, "sha512", "sha256")
+ c.Assert(err, IsNil)
+
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ tmp.writeStagedTarget("foo.txt", "foo")
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+ tmp.writeStagedTarget("dir/bar.txt", "bar")
+ c.Assert(r.AddTarget("dir/bar.txt", nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ versions, err := r.fileVersions()
+ c.Assert(err, IsNil)
+ c.Assert(versions["root.json"], Equals, int64(1))
+ c.Assert(versions["targets.json"], Equals, int64(1))
+ c.Assert(versions["snapshot.json"], Equals, int64(1))
+
+ hashes, err := r.fileHashes()
+ c.Assert(err, IsNil)
+
+ // root.json, targets.json and snapshot.json should exist at both versioned and unversioned paths
+ for _, meta := range []string{"root.json", "targets.json", "snapshot.json"} {
+ repoPath := path.Join("repository", meta)
+ if meta != "root.json" {
+ c.Assert(len(hashes[meta]) > 0, Equals, true)
+ }
+ tmp.assertHashedFilesNotExist(repoPath, hashes[meta])
+ tmp.assertVersionedFileExist(repoPath, versions[meta])
+ tmp.assertExists(repoPath)
+ }
+
+ // target files should exist at hashed but not unhashed paths
+ for _, target := range []string{"targets/foo.txt", "targets/dir/bar.txt"} {
+ repoPath := path.Join("repository", target)
+ tmp.assertHashedFilesExist(repoPath, hashes[target])
+ tmp.assertNotExist(repoPath)
+ }
+
+ // timestamp.json should exist at an unversioned and unhashed path (it doesn't have a hash)
+ c.Assert(hashes["repository/timestamp.json"], IsNil)
+ tmp.assertVersionedFileNotExist("repository/timestamp.json", versions["repository/timestamp.json"])
+ tmp.assertExists("repository/timestamp.json")
+
+ // removing a file should remove the hashed files
+ c.Assert(r.RemoveTarget("foo.txt"), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ versions, err = r.fileVersions()
+ c.Assert(err, IsNil)
+ c.Assert(versions["root.json"], Equals, int64(1))
+ c.Assert(versions["targets.json"], Equals, int64(2))
+ c.Assert(versions["snapshot.json"], Equals, int64(2))
+
+ // Save the old hashes for foo.txt to make sure we can assert it doesn't exist later.
+ fooHashes := hashes["targets/foo.txt"]
+ hashes, err = r.fileHashes()
+ c.Assert(err, IsNil)
+
+ // root.json, targets.json and snapshot.json should exist at both versioned and unversioned paths
+ for _, meta := range []string{"root.json", "targets.json", "snapshot.json"} {
+ repoPath := path.Join("repository", meta)
+ if meta != "root.json" {
+ c.Assert(len(hashes[meta]) > 0, Equals, true)
+ }
+ tmp.assertHashedFilesNotExist(repoPath, hashes[meta])
+ tmp.assertVersionedFileExist(repoPath, versions[meta])
+ tmp.assertExists(repoPath)
+ }
+
+ tmp.assertHashedFilesNotExist("repository/targets/foo.txt", fooHashes)
+ tmp.assertNotExist("repository/targets/foo.txt")
+
+ // targets should be returned by new repo
+ newRepo, err := NewRepo(local, "sha512", "sha256")
+ c.Assert(err, IsNil)
+ t, err := newRepo.topLevelTargets()
+ c.Assert(err, IsNil)
+ c.Assert(t.Targets, HasLen, 1)
+ if _, ok := t.Targets["dir/bar.txt"]; !ok {
+ c.Fatal("missing targets file: dir/bar.txt")
+ }
+}
+
+func (rs *RepoSuite) TestExpiresAndVersion(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ past := time.Now().Add(-1 * time.Second)
+ _, genKeyErr := r.GenKeyWithExpires("root", past)
+ for _, err := range []error{
+ genKeyErr,
+ r.AddTargetWithExpires("foo.txt", nil, past),
+ r.RemoveTargetWithExpires("foo.txt", past),
+ r.SnapshotWithExpires(past),
+ r.TimestampWithExpires(past),
+ } {
+ c.Assert(err, Equals, ErrInvalidExpires{past})
+ }
+
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ c.Assert(r.AddTargets([]string{}, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Version, Equals, int64(1))
+
+ expires := time.Now().Add(24 * time.Hour)
+ _, err = r.GenKeyWithExpires("root", expires)
+ c.Assert(err, IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ root, err = r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Expires.Unix(), DeepEquals, expires.Round(time.Second).Unix())
+ c.Assert(root.Version, Equals, int64(2))
+
+ expires = time.Now().Add(12 * time.Hour)
+ role, ok := root.Roles["root"]
+ if !ok {
+ c.Fatal("missing root role")
+ }
+ c.Assert(role.KeyIDs, HasLen, 2)
+ c.Assert(r.RevokeKeyWithExpires("root", role.KeyIDs[0], expires), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ root, err = r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Expires.Unix(), DeepEquals, expires.Round(time.Second).Unix())
+ c.Assert(root.Version, Equals, int64(3))
+
+ expires = time.Now().Add(6 * time.Hour)
+ c.Assert(r.AddTargetWithExpires("foo.txt", nil, expires), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ targets, err := r.topLevelTargets()
+ c.Assert(err, IsNil)
+ c.Assert(targets.Expires.Unix(), Equals, expires.Round(time.Second).Unix())
+ c.Assert(targets.Version, Equals, int64(2))
+
+ expires = time.Now().Add(2 * time.Hour)
+ c.Assert(r.RemoveTargetWithExpires("foo.txt", expires), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ targets, err = r.topLevelTargets()
+ c.Assert(err, IsNil)
+ c.Assert(targets.Expires.Unix(), Equals, expires.Round(time.Second).Unix())
+ c.Assert(targets.Version, Equals, int64(3))
+
+ expires = time.Now().Add(time.Hour)
+ c.Assert(r.SnapshotWithExpires(expires), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ snapshot, err := r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Expires.Unix(), Equals, expires.Round(time.Second).Unix())
+ c.Assert(snapshot.Version, Equals, int64(6))
+
+ _, snapshotHasRoot := snapshot.Meta["root.json"]
+ c.Assert(snapshotHasRoot, Equals, false)
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, targets.Version)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Version, Equals, int64(7))
+
+ expires = time.Now().Add(10 * time.Minute)
+ c.Assert(r.TimestampWithExpires(expires), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ timestamp, err := r.timestamp()
+ c.Assert(err, IsNil)
+ c.Assert(timestamp.Expires.Unix(), Equals, expires.Round(time.Second).Unix())
+ c.Assert(timestamp.Version, Equals, int64(8))
+
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ timestamp, err = r.timestamp()
+ c.Assert(err, IsNil)
+ c.Assert(timestamp.Version, Equals, int64(9))
+ c.Assert(timestamp.Meta["snapshot.json"].Version, Equals, snapshot.Version)
+}
+
+func (rs *RepoSuite) TestHashAlgorithm(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ type hashTest struct {
+ args []string
+ expected []string
+ }
+ for _, test := range []hashTest{
+ {args: []string{}, expected: []string{"sha512"}},
+ {args: []string{"sha256"}},
+ {args: []string{"sha512", "sha256"}},
+ } {
+ // generate metadata with specific hash functions
+ r, err := NewRepo(local, test.args...)
+ c.Assert(err, IsNil)
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+
+ // check metadata has correct hash functions
+ if test.expected == nil {
+ test.expected = test.args
+ }
+ targets, err := r.topLevelTargets()
+ c.Assert(err, IsNil)
+ snapshot, err := r.snapshot()
+ c.Assert(err, IsNil)
+ timestamp, err := r.timestamp()
+ c.Assert(err, IsNil)
+ for name, file := range map[string]data.FileMeta{
+ "foo.txt": targets.Targets["foo.txt"].FileMeta,
+ "targets.json": {Length: snapshot.Meta["targets.json"].Length, Hashes: snapshot.Meta["targets.json"].Hashes},
+ "snapshot.json": {Length: timestamp.Meta["snapshot.json"].Length, Hashes: timestamp.Meta["snapshot.json"].Hashes},
+ } {
+ for _, hashAlgorithm := range test.expected {
+ if _, ok := file.Hashes[hashAlgorithm]; !ok {
+ c.Fatalf("expected %s hash to contain hash func %s, got %s", name, hashAlgorithm, file.Hashes.HashAlgorithms())
+ }
+ }
+ }
+ }
+}
+
+func (rs *RepoSuite) TestKeyPersistence(c *C) {
+ tmp := newTmpDir(c)
+ oldPassphrase := []byte("old_s3cr3t")
+ newPassphrase := []byte("new_s3cr3t")
+ // returnNewPassphrase is used to force the passphrase function to return the new passphrase when called by the SaveSigner() method
+ returnNewPassphrase := false
+ // passphrase mock function
+ testPassphraseFunc := func(a string, b, change bool) ([]byte, error) {
+ if change || returnNewPassphrase {
+ return newPassphrase, nil
+ }
+ return oldPassphrase, nil
+ }
+ store := FileSystemStore(tmp.path, testPassphraseFunc)
+
+ assertKeys := func(role string, enc bool, expected []*data.PrivateKey) {
+ keysJSON := tmp.readFile("keys/" + role + ".json")
+ pk := &persistedKeys{}
+ c.Assert(json.Unmarshal(keysJSON, pk), IsNil)
+
+ // check the persisted keys are correct
+ var actual []*data.PrivateKey
+ pass := oldPassphrase
+ if enc {
+ c.Assert(pk.Encrypted, Equals, true)
+ if returnNewPassphrase {
+ pass = newPassphrase
+ }
+ decrypted, err := encrypted.Decrypt(pk.Data, pass)
+ c.Assert(err, IsNil)
+ c.Assert(json.Unmarshal(decrypted, &actual), IsNil)
+ } else {
+ c.Assert(pk.Encrypted, Equals, false)
+ c.Assert(json.Unmarshal(pk.Data, &actual), IsNil)
+ }
+
+ // Compare slices of unique elements disregarding order.
+ c.Assert(actual, HasLen, len(expected))
+ for _, gotKey := range actual {
+ expectedNumMatches := 0
+ for _, x := range actual {
+ if reflect.DeepEqual(gotKey, x) {
+ expectedNumMatches++
+ }
+ }
+
+ numMatches := 0
+ for _, wantKey := range expected {
+ wantCanon, err := cjson.EncodeCanonical(wantKey)
+ c.Assert(err, IsNil)
+
+ gotCanon, err := cjson.EncodeCanonical(gotKey)
+ c.Assert(err, IsNil)
+
+ if string(wantCanon) == string(gotCanon) {
+ numMatches++
+ }
+ }
+
+ c.Assert(numMatches, Equals, expectedNumMatches, Commentf("actual: %+v, expected: %+v", actual, expected))
+ }
+
+ // check GetKeys is correct
+ signers, err := store.GetSigners(role)
+ c.Assert(err, IsNil)
+
+ // Compare slices of unique elements disregarding order.
+ c.Assert(signers, HasLen, len(expected))
+ for _, s := range signers {
+ expectedNumMatches := 0
+ for _, x := range signers {
+ if reflect.DeepEqual(s, x) {
+ expectedNumMatches++
+ }
+ }
+
+ numMatches := 0
+ for _, e := range expected {
+ v, err := keys.GetSigner(e)
+ c.Assert(err, IsNil)
+
+ if reflect.DeepEqual(s.PublicData().IDs(), v.PublicData().IDs()) {
+ numMatches++
+ }
+ }
+
+ c.Assert(numMatches, Equals, expectedNumMatches, Commentf("signers: %+v, expected: %+v", signers, expected))
+ }
+ }
+
+ // save a key and check it gets encrypted
+ signer, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ privateKey, err := signer.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ c.Assert(store.SaveSigner("root", signer), IsNil)
+ assertKeys("root", true, []*data.PrivateKey{privateKey})
+
+ // save another key and check it gets added to the existing keys
+ newKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ newPrivateKey, err := newKey.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ c.Assert(store.SaveSigner("root", newKey), IsNil)
+ assertKeys("root", true, []*data.PrivateKey{privateKey, newPrivateKey})
+
+ // check saving a key to an encrypted file without a passphrase fails
+ insecureStore := FileSystemStore(tmp.path, nil)
+ signer, err = keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(insecureStore.SaveSigner("root", signer), Equals, ErrPassphraseRequired{"root"})
+
+ // save a key to an insecure store and check it is not encrypted
+ signer, err = keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ privateKey, err = signer.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ c.Assert(insecureStore.SaveSigner("targets", signer), IsNil)
+ assertKeys("targets", false, []*data.PrivateKey{privateKey})
+
+ c.Assert(insecureStore.SaveSigner("foo", signer), IsNil)
+ assertKeys("foo", false, []*data.PrivateKey{privateKey})
+
+ // Test changing the passphrase
+ // 1. Create a secure store with a passphrase (create new object and temp folder so we discard any previous state)
+ tmp = newTmpDir(c)
+ var logBytes bytes.Buffer
+ storeOpts := StoreOpts{
+ Logger: log.New(&logBytes, "", 0),
+ PassFunc: testPassphraseFunc,
+ }
+ store = FileSystemStoreWithOpts(tmp.path, storeOpts)
+
+ // 1.5. Changing passphrase works for top-level and delegated roles.
+ r, err := NewRepo(store)
+ c.Assert(err, IsNil)
+
+ c.Assert(r.ChangePassphrase("targets"), NotNil)
+ c.Assert(r.ChangePassphrase("foo"), NotNil)
+
+ // 2. Test changing the passphrase when the keys file does not exist - should FAIL
+ c.Assert(store.(PassphraseChanger).ChangePassphrase("root"), NotNil)
+ c.Assert(strings.Contains(logBytes.String(), "Missing keys file"), Equals, true)
+
+ // 3. Generate a new key
+ signer, err = keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ privateKey, err = signer.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ c.Assert(store.SaveSigner("root", signer), IsNil)
+
+ // 4. Verify the key file can be decrypted using the original passphrase - should SUCCEED
+ assertKeys("root", true, []*data.PrivateKey{privateKey})
+
+ // 5. Change the passphrase (our mock passphrase function is called with change=true thus returning the newPassphrase value)
+ c.Assert(store.(PassphraseChanger).ChangePassphrase("root"), IsNil)
+
+ // 6. Try to add a key and implicitly decrypt the keys file using the OLD passphrase - should FAIL
+ newKey, err = keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ _, err = newKey.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ c.Assert(store.SaveSigner("root", newKey), NotNil)
+
+ // 7. Try to add a key and implicitly decrypt the keys using the NEW passphrase - should SUCCEED
+ returnNewPassphrase = true
+ newKey, err = keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ newPrivateKey, err = newKey.MarshalPrivateKey()
+ c.Assert(err, IsNil)
+ c.Assert(store.SaveSigner("root", newKey), IsNil)
+
+ // 8. Verify again that the key entries are what we expect after decrypting them using the NEW passphrase
+ assertKeys("root", true, []*data.PrivateKey{privateKey, newPrivateKey})
+}
+
+func (rs *RepoSuite) TestManageMultipleTargets(c *C) {
+ tmp := newTmpDir(c)
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+ // don't use consistent snapshots to make the checks simpler
+ c.Assert(r.Init(false), IsNil)
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ assertRepoTargets := func(paths ...string) {
+ t, err := r.topLevelTargets()
+ c.Assert(err, IsNil)
+ for _, path := range paths {
+ if _, ok := t.Targets[path]; !ok {
+ c.Fatalf("missing target file: %s", path)
+ }
+ }
+ }
+
+ // adding and committing multiple files moves correct targets from staged -> repository
+ tmp.writeStagedTarget("foo.txt", "foo")
+ tmp.writeStagedTarget("bar.txt", "bar")
+ c.Assert(r.AddTargets([]string{"foo.txt", "bar.txt"}, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ assertRepoTargets("foo.txt", "bar.txt")
+ tmp.assertExists("repository/targets/foo.txt")
+ tmp.assertExists("repository/targets/bar.txt")
+
+ // adding all targets moves them all from staged -> repository
+ count := 10
+ files := make([]string, count)
+ for i := 0; i < count; i++ {
+ files[i] = fmt.Sprintf("file%d.txt", i)
+ tmp.writeStagedTarget(files[i], "data")
+ }
+ c.Assert(r.AddTargets(nil, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ tmp.assertExists("repository/targets/foo.txt")
+ tmp.assertExists("repository/targets/bar.txt")
+ assertRepoTargets(files...)
+ for _, file := range files {
+ tmp.assertExists("repository/targets/" + file)
+ }
+ tmp.assertEmpty("staged/targets")
+ tmp.assertEmpty("staged")
+
+ // removing all targets removes them from the repository and targets.json
+ c.Assert(r.RemoveTargets(nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+ tmp.assertNotExist("repository/targets")
+ t, err := r.topLevelTargets()
+ c.Assert(err, IsNil)
+ c.Assert(t.Targets, HasLen, 0)
+}
+
+func (rs *RepoSuite) TestCustomTargetMetadata(c *C) {
+ files := map[string][]byte{
+ "foo.txt": []byte("foo"),
+ "bar.txt": []byte("bar"),
+ "baz.txt": []byte("baz"),
+ }
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ generateAndAddPrivateKey(c, r, "targets")
+
+ custom := json.RawMessage(`{"foo":"bar"}`)
+ assertCustomMeta := func(file string, custom *json.RawMessage) {
+ t, err := r.topLevelTargets()
+ c.Assert(err, IsNil)
+ target, ok := t.Targets[file]
+ if !ok {
+ c.Fatalf("missing target file: %s", file)
+ }
+ c.Assert(target.Custom, DeepEquals, custom)
+ }
+
+ // check custom metadata gets added to the target
+ c.Assert(r.AddTarget("foo.txt", custom), IsNil)
+ assertCustomMeta("foo.txt", &custom)
+
+ // check adding bar.txt with no metadata doesn't affect foo.txt
+ c.Assert(r.AddTarget("bar.txt", nil), IsNil)
+ assertCustomMeta("bar.txt", nil)
+ assertCustomMeta("foo.txt", &custom)
+
+ // check adding all files with no metadata doesn't reset existing metadata
+ c.Assert(r.AddTargets(nil, nil), IsNil)
+ assertCustomMeta("baz.txt", nil)
+ assertCustomMeta("bar.txt", nil)
+ assertCustomMeta("foo.txt", &custom)
+}
+
+func (rs *RepoSuite) TestUnknownKeyIDs(c *C) {
+ // generate a repo
+ local := MemoryStore(make(map[string]json.RawMessage), nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ // add a new key to the root metadata with an unknown key id.
+ signer, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+
+ root, err := r.root()
+ c.Assert(err, IsNil)
+ c.Assert(root.Version, Equals, int64(1))
+
+ root.Keys["unknown-key-id"] = signer.PublicData()
+ r.setMeta("root.json", root)
+
+ // commit the metadata to the store.
+ c.Assert(r.AddTargets([]string{}, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ // validate that the unknown key id wasn't stripped when written to the
+ // store.
+ meta, err := local.GetMeta()
+ c.Assert(err, IsNil)
+
+ rootJSON, ok := meta["root.json"]
+ c.Assert(ok, Equals, true)
+
+ var signedRoot struct {
+ Signed data.Root `json:"signed"`
+ Signatures []data.Signature `json:"signatures"`
+ }
+ c.Assert(json.Unmarshal(rootJSON, &signedRoot), IsNil)
+ c.Assert(signedRoot.Signed.Version, Equals, int64(1))
+
+ unknownKey, ok := signedRoot.Signed.Keys["unknown-key-id"]
+ c.Assert(ok, Equals, true)
+ c.Assert(unknownKey, DeepEquals, signer.PublicData())
+
+ // a new root should preserve the unknown key id.
+ root, err = r.root()
+ c.Assert(root, NotNil)
+ c.Assert(err, IsNil)
+
+ genKey(c, r, "timestamp")
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ meta, err = local.GetMeta()
+ c.Assert(err, IsNil)
+
+ rootJSON, ok = meta["root.json"]
+ c.Assert(ok, Equals, true)
+
+ c.Assert(json.Unmarshal(rootJSON, &signedRoot), IsNil)
+ c.Assert(signedRoot.Signed.Version, Equals, int64(2))
+
+ unknownKey, ok = signedRoot.Signed.Keys["unknown-key-id"]
+ c.Assert(ok, Equals, true)
+ c.Assert(unknownKey, DeepEquals, signer.PublicData())
+}
+
+func (rs *RepoSuite) TestThreshold(c *C) {
+ local := MemoryStore(make(map[string]json.RawMessage), nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ _, err = r.GetThreshold("root")
+ c.Assert(err, DeepEquals, ErrInvalidRole{"root", "role missing from root metadata"})
+ err = r.SetThreshold("root", 2)
+ c.Assert(err, DeepEquals, ErrInvalidRole{"root", "role missing from root metadata"})
+
+ // Add one key to each role
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ t, err := r.GetThreshold("root")
+ c.Assert(err, IsNil)
+ c.Assert(t, Equals, 1)
+
+ _, err = r.GetThreshold("foo")
+ c.Assert(err, DeepEquals, ErrInvalidRole{"foo", "only thresholds for top-level roles supported"})
+ err = r.SetThreshold("foo", 2)
+ c.Assert(err, DeepEquals, ErrInvalidRole{"foo", "only thresholds for top-level roles supported"})
+
+ // commit the metadata to the store.
+ c.Assert(r.AddTargets([]string{}, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ // Set a new threshold. Commit without threshold keys
+ c.Assert(r.SetThreshold("root", 2), IsNil)
+ t, err = r.GetThreshold("root")
+ c.Assert(err, IsNil)
+ c.Assert(t, Equals, 2)
+ c.Assert(r.Commit(), DeepEquals, ErrNotEnoughKeys{"root", 1, 2})
+
+ // Add a second root key and try again
+ genKey(c, r, "root")
+ c.Assert(r.Sign("root.json"), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ // Check versions updated
+ rootVersion, err := r.RootVersion()
+ c.Assert(err, IsNil)
+ c.Assert(rootVersion, Equals, int64(2))
+
+ targetsVersion, err := r.TargetsVersion()
+ c.Assert(err, IsNil)
+ c.Assert(targetsVersion, Equals, int64(1))
+
+ snapshotVersion, err := r.SnapshotVersion()
+ c.Assert(err, IsNil)
+ c.Assert(snapshotVersion, Equals, int64(2))
+
+ timestampVersion, err := r.TimestampVersion()
+ c.Assert(err, IsNil)
+ c.Assert(timestampVersion, Equals, int64(2))
+}
+
+func (rs *RepoSuite) TestAddOrUpdateSignatures(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // don't use consistent snapshots to make the checks simpler
+ c.Assert(r.Init(false), IsNil)
+
+ // generate root key offline and add as a verification key
+ rootKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("root", rootKey.PublicData()), IsNil)
+ targetsKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("targets", targetsKey.PublicData()), IsNil)
+ snapshotKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("snapshot", snapshotKey.PublicData()), IsNil)
+ timestampKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("timestamp", timestampKey.PublicData()), IsNil)
+
+ // generate signatures externally and append
+ rootMeta, err := r.SignedMeta("root.json")
+ c.Assert(err, IsNil)
+ rootCanonical, err := cjson.EncodeCanonical(rootMeta.Signed)
+ c.Assert(err, IsNil)
+ rootSig, err := rootKey.SignMessage(rootCanonical)
+ c.Assert(err, IsNil)
+ for _, id := range rootKey.PublicData().IDs() {
+ c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
+ KeyID: id,
+ Signature: rootSig}), IsNil)
+ }
+
+ // add targets and sign
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+ targetsMeta, err := r.SignedMeta("targets.json")
+ c.Assert(err, IsNil)
+ targetsCanonical, err := cjson.EncodeCanonical(targetsMeta.Signed)
+ c.Assert(err, IsNil)
+ targetsSig, err := targetsKey.SignMessage(targetsCanonical)
+ c.Assert(err, IsNil)
+ for _, id := range targetsKey.PublicData().IDs() {
+ r.AddOrUpdateSignature("targets.json", data.Signature{
+ KeyID: id,
+ Signature: targetsSig})
+ }
+
+ // snapshot and timestamp
+ c.Assert(r.Snapshot(), IsNil)
+ snapshotMeta, err := r.SignedMeta("snapshot.json")
+ c.Assert(err, IsNil)
+ snapshotCanonical, err := cjson.EncodeCanonical(snapshotMeta.Signed)
+ c.Assert(err, IsNil)
+ snapshotSig, err := snapshotKey.SignMessage(snapshotCanonical)
+ c.Assert(err, IsNil)
+ for _, id := range snapshotKey.PublicData().IDs() {
+ r.AddOrUpdateSignature("snapshot.json", data.Signature{
+ KeyID: id,
+ Signature: snapshotSig})
+ }
+
+ c.Assert(r.Timestamp(), IsNil)
+ timestampMeta, err := r.SignedMeta("timestamp.json")
+ c.Assert(err, IsNil)
+ timestampCanonical, err := cjson.EncodeCanonical(timestampMeta.Signed)
+ c.Assert(err, IsNil)
+ timestampSig, err := timestampKey.SignMessage(timestampCanonical)
+ c.Assert(err, IsNil)
+ for _, id := range timestampKey.PublicData().IDs() {
+ r.AddOrUpdateSignature("timestamp.json", data.Signature{
+ KeyID: id,
+ Signature: timestampSig})
+ }
+
+ // commit successfully!
+ c.Assert(r.Commit(), IsNil)
+}
+
+func (rs *RepoSuite) TestBadAddOrUpdateSignatures(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // don't use consistent snapshots to make the checks simpler
+ c.Assert(r.Init(false), IsNil)
+
+ c.Assert(r.AddOrUpdateSignature("targets.json", data.Signature{
+ KeyID: "foo",
+ Signature: nil}), Equals, ErrInvalidRole{"targets", "role is not in verifier DB"})
+
+ // generate root key offline and add as a verification key
+ rootKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("root", rootKey.PublicData()), IsNil)
+ targetsKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("targets", targetsKey.PublicData()), IsNil)
+ snapshotKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("snapshot", snapshotKey.PublicData()), IsNil)
+ timestampKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ c.Assert(r.AddVerificationKey("timestamp", timestampKey.PublicData()), IsNil)
+
+ // attempt to sign `root`, rather than `root.json`
+ for _, id := range rootKey.PublicData().IDs() {
+ c.Assert(r.AddOrUpdateSignature("root", data.Signature{
+ KeyID: id,
+ Signature: nil}), Equals, ErrMissingMetadata{"root"})
+ }
+
+ // add a signature with a bad role
+ rootMeta, err := r.SignedMeta("root.json")
+ c.Assert(err, IsNil)
+ rootCanonical, err := cjson.EncodeCanonical(rootMeta.Signed)
+ c.Assert(err, IsNil)
+ rootSig, err := rootKey.Sign(rand.Reader, rootCanonical, crypto.Hash(0))
+ c.Assert(err, IsNil)
+ for _, id := range rootKey.PublicData().IDs() {
+ c.Assert(r.AddOrUpdateSignature("invalid_root.json", data.Signature{
+ KeyID: id,
+ Signature: rootSig}), Equals, ErrInvalidRole{"invalid_root", "no trusted keys for role"})
+ }
+
+ // add a root signature with an key ID that is for the targets role
+ for _, id := range targetsKey.PublicData().IDs() {
+ c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
+ KeyID: id,
+ Signature: rootSig}), Equals, verify.ErrInvalidKey)
+ }
+
+ // attempt to add a bad signature to root
+ badSig, err := rootKey.Sign(rand.Reader, []byte(""), crypto.Hash(0))
+ c.Assert(err, IsNil)
+ for _, id := range rootKey.PublicData().IDs() {
+ c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
+ KeyID: id,
+ Signature: badSig}), Equals, verify.ErrInvalid)
+ }
+
+ // add the correct root signature
+ for _, id := range rootKey.PublicData().IDs() {
+ c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
+ KeyID: id,
+ Signature: rootSig}), IsNil)
+ }
+ checkSigIDs := func(role string) {
+ s, err := r.SignedMeta(role)
+ c.Assert(err, IsNil)
+ db, err := r.topLevelKeysDB()
+ c.Assert(err, IsNil)
+ // keys is a map of key IDs.
+ keys := db.GetRole(strings.TrimSuffix(role, ".json")).KeyIDs
+ c.Assert(s.Signatures, HasLen, len(keys))
+ // If the lengths are equal, and each signature key ID appears
+ // in the role keys, they Sig IDs are equal to keyIDs.
+ for _, sig := range s.Signatures {
+ if _, ok := keys[sig.KeyID]; !ok {
+ c.Fatal("missing key ID in signatures")
+ }
+ }
+ }
+ checkSigIDs("root.json")
+
+ // re-adding should not duplicate. this is checked by verifying
+ // signature key IDs match with the map of role key IDs.
+ for _, id := range rootKey.PublicData().IDs() {
+ c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
+ KeyID: id,
+ Signature: rootSig}), IsNil)
+ }
+ checkSigIDs("root.json")
+}
+
+func (rs *RepoSuite) TestSignDigest(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ digest := "sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"
+ hash := "bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"
+ size := int64(42)
+
+ c.Assert(r.AddTargetsWithDigest(hash, "sha256", size, digest, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ digest_bytes, err := hex.DecodeString("bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c")
+ hex_digest_bytes := data.HexBytes(digest_bytes)
+ c.Assert(err, IsNil)
+
+ targets, err := r.topLevelTargets()
+ c.Assert(err, IsNil)
+ c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Length, Equals, size)
+ c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Hashes["sha256"], DeepEquals, hex_digest_bytes)
+}
+
+func concat(ss ...[]string) []string {
+ ret := []string{}
+ for _, s := range ss {
+ ret = append(ret, s...)
+ }
+ return ret
+}
+
+func checkSigKeyIDs(c *C, local LocalStore, fileToKeyIDs map[string][]string) {
+ metas, err := local.GetMeta()
+ c.Assert(err, IsNil)
+
+ for f, keyIDs := range fileToKeyIDs {
+ meta, ok := metas[f]
+ c.Assert(ok, Equals, true, Commentf("meta file: %v", f))
+
+ s := &data.Signed{}
+ err = json.Unmarshal(meta, s)
+ c.Assert(err, IsNil)
+
+ gotKeyIDs := []string{}
+ for _, sig := range s.Signatures {
+ gotKeyIDs = append(gotKeyIDs, sig.KeyID)
+ }
+ gotKeyIDs = sets.DeduplicateStrings(gotKeyIDs)
+ sort.Strings(gotKeyIDs)
+
+ sort.Strings(keyIDs)
+ c.Assert(gotKeyIDs, DeepEquals, keyIDs)
+ }
+}
+
+func (rs *RepoSuite) TestDelegations(c *C) {
+ tmp := newTmpDir(c)
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // Add one key to each role
+ genKey(c, r, "root")
+ targetsKeyIDs := genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ // commit the metadata to the store.
+ c.Assert(r.AddTargets([]string{}, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err := r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 1)
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "1.targets.json": targetsKeyIDs,
+ })
+
+ saveNewKey := func(role string) keys.Signer {
+ key, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+
+ err = local.SaveSigner(role, key)
+ c.Assert(err, IsNil)
+
+ return key
+ }
+
+ // Delegate from targets -> role1 for A/*, B/* with one key, threshold 1.
+ role1ABKey := saveNewKey("role1")
+ role1AB := data.DelegatedRole{
+ Name: "role1",
+ KeyIDs: role1ABKey.PublicData().IDs(),
+ Paths: []string{"A/*", "B/*"},
+ Threshold: 1,
+ }
+ err = r.AddDelegatedRole("targets", role1AB, []*data.PublicKey{
+ role1ABKey.PublicData(),
+ })
+ c.Assert(err, IsNil)
+
+ // Adding duplicate delegation should return an error.
+ err = r.AddDelegatedRole("targets", role1AB, []*data.PublicKey{
+ role1ABKey.PublicData(),
+ })
+ c.Assert(err, NotNil)
+
+ // Delegate from targets -> role2 for C/*, D/* with three key, threshold 2.
+ role2CDKey1 := saveNewKey("role2")
+ role2CDKey2 := saveNewKey("role2")
+ role2CDKey3 := saveNewKey("role2")
+ role2CD := data.DelegatedRole{
+ Name: "role2",
+ KeyIDs: concat(
+ role2CDKey1.PublicData().IDs(),
+ role2CDKey2.PublicData().IDs(),
+ role2CDKey3.PublicData().IDs(),
+ ),
+ Paths: []string{"C/*", "D/*"},
+ Threshold: 2,
+ }
+ err = r.AddDelegatedRole("targets", role2CD, []*data.PublicKey{
+ role2CDKey1.PublicData(),
+ role2CDKey2.PublicData(),
+ role2CDKey3.PublicData(),
+ })
+ c.Assert(err, IsNil)
+
+ // Delegate from role1 -> role2 for A/allium.txt with one key, threshold 1.
+ role1To2Key := saveNewKey("role2")
+ role1To2 := data.DelegatedRole{
+ Name: "role2",
+ KeyIDs: role1To2Key.PublicData().IDs(),
+ Paths: []string{"A/allium.txt"},
+ Threshold: 1,
+ Terminating: true,
+ }
+ err = r.AddDelegatedRole("role1", role1To2, []*data.PublicKey{
+ role1To2Key.PublicData(),
+ })
+ c.Assert(err, IsNil)
+
+ checkDelegations := func(delegator string, delegatedRoles ...data.DelegatedRole) {
+ t, err := r.targets(delegator)
+ c.Assert(err, IsNil)
+
+ // Check if there are no delegations.
+ if t.Delegations == nil {
+ if delegatedRoles != nil {
+ c.Fatal("expected delegated roles on delegator")
+ }
+ return
+ }
+
+ // Check that delegated roles are copied verbatim.
+ c.Assert(t.Delegations.Roles, DeepEquals, delegatedRoles)
+
+ // Check that public keys match key IDs in roles.
+ expectedKeyIDs := []string{}
+ for _, dr := range delegatedRoles {
+ expectedKeyIDs = append(expectedKeyIDs, dr.KeyIDs...)
+ }
+ expectedKeyIDs = sets.DeduplicateStrings(expectedKeyIDs)
+ sort.Strings(expectedKeyIDs)
+
+ gotKeyIDs := []string{}
+ for _, k := range t.Delegations.Keys {
+ gotKeyIDs = append(gotKeyIDs, k.IDs()...)
+ }
+ gotKeyIDs = sets.DeduplicateStrings(gotKeyIDs)
+ sort.Strings(gotKeyIDs)
+
+ c.Assert(gotKeyIDs, DeepEquals, expectedKeyIDs)
+ }
+
+ checkDelegations("targets", role1AB, role2CD)
+ checkDelegations("role1", role1To2)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 3)
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(2))
+ c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(1))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "2.targets.json": targetsKeyIDs,
+ "1.role1.json": role1ABKey.PublicData().IDs(),
+ "1.role2.json": concat(
+ role2CDKey1.PublicData().IDs(),
+ role2CDKey2.PublicData().IDs(),
+ role2CDKey3.PublicData().IDs(),
+ role1To2Key.PublicData().IDs(),
+ ),
+ })
+
+ // Add a variety of targets.
+ files := map[string]string{
+ // targets.json
+ "potato.txt": "potatoes can be starchy or waxy",
+ // role1.json
+ "A/apple.txt": "apples are sometimes red",
+ "B/banana.txt": "bananas are yellow and sometimes brown",
+ // role2.json
+ "C/clementine.txt": "clementines are a citrus fruit",
+ "D/durian.txt": "durians are spiky",
+ "A/allium.txt": "alliums include garlic and leeks",
+ }
+ for name, content := range files {
+ tmp.writeStagedTarget(name, content)
+ c.Assert(r.AddTarget(name, nil), IsNil)
+ }
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 3)
+ // All roles should have new targets.
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
+ c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(2))
+ c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(2))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "3.targets.json": targetsKeyIDs,
+ "2.role1.json": role1ABKey.PublicData().IDs(),
+ "2.role2.json": concat(
+ role2CDKey1.PublicData().IDs(),
+ role2CDKey2.PublicData().IDs(),
+ role2CDKey3.PublicData().IDs(),
+ role1To2Key.PublicData().IDs(),
+ ),
+ })
+
+ // Check that the given targets role has signed for the given filenames, with
+ // the correct file metadata.
+ checkTargets := func(role string, filenames ...string) {
+ t, err := r.targets(role)
+ c.Assert(err, IsNil)
+ c.Assert(t.Targets, HasLen, len(filenames))
+
+ for _, fn := range filenames {
+ content := files[fn]
+
+ fm, err := util.GenerateTargetFileMeta(strings.NewReader(content))
+ c.Assert(err, IsNil)
+
+ c.Assert(util.TargetFileMetaEqual(t.Targets[fn], fm), IsNil)
+ }
+ }
+
+ checkTargets("targets", "potato.txt")
+ checkTargets("role1", "A/apple.txt", "B/banana.txt")
+ checkTargets("role2", "C/clementine.txt", "D/durian.txt", "A/allium.txt")
+
+ // Test AddTargetToPreferredRole.
+ // role2 is the default signer for A/allium.txt, but role1 is also eligible
+ // for A/*.txt according to the delegation from the top-level targets role.
+ c.Assert(r.RemoveTarget("A/allium.txt"), IsNil)
+ tmp.writeStagedTarget("A/allium.txt", files["A/allium.txt"])
+ c.Assert(r.AddTargetToPreferredRole("A/allium.txt", nil, "role1"), IsNil)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 3)
+ // Only role1 and role2 should have bumped versions.
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
+ c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(3))
+ c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(3))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "3.targets.json": targetsKeyIDs,
+ "3.role1.json": role1ABKey.PublicData().IDs(),
+ "3.role2.json": concat(
+ role2CDKey1.PublicData().IDs(),
+ role2CDKey2.PublicData().IDs(),
+ role2CDKey3.PublicData().IDs(),
+ role1To2Key.PublicData().IDs(),
+ ),
+ })
+
+ // role1 now signs A/allium.txt.
+ checkTargets("targets", "potato.txt")
+ checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt")
+ checkTargets("role2", "C/clementine.txt", "D/durian.txt")
+
+ // Remove the delegation from role1 to role2.
+ c.Assert(r.ResetTargetsDelegations("role1"), IsNil)
+ checkDelegations("targets", role1AB, role2CD)
+ checkDelegations("role1")
+
+ // Try to sign A/allium.txt with role2.
+ // It should fail since we removed the role1 -> role2 delegation.
+ c.Assert(r.RemoveTarget("A/allium.txt"), IsNil)
+ tmp.writeStagedTarget("A/allium.txt", files["A/allium.txt"])
+ c.Assert(r.AddTargetToPreferredRole("A/allium.txt", nil, "role2"), Equals, ErrNoDelegatedTarget{Path: "A/allium.txt"})
+
+ // Try to sign A/allium.txt with the default role (role1).
+ c.Assert(r.AddTarget("A/allium.txt", nil), IsNil)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 3)
+ // Only role1 should have a bumped version.
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
+ c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(4))
+ c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(3))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "3.targets.json": targetsKeyIDs,
+ "4.role1.json": role1ABKey.PublicData().IDs(),
+ "3.role2.json": concat(
+ // Metadata (and therefore signers) for role2.json shouldn't have
+ // changed, even though we revoked role1To2Key. Clients verify the
+ // signature using keys specified by 4.role1.json, so role1To2Key
+ // shouldn't contribute to the threshold.
+ role2CDKey1.PublicData().IDs(),
+ role2CDKey2.PublicData().IDs(),
+ role2CDKey3.PublicData().IDs(),
+ role1To2Key.PublicData().IDs(),
+ ),
+ })
+
+ // Re-sign target signed by role2 to test that role1To2Key is not used going
+ // forward.
+ c.Assert(r.RemoveTarget("C/clementine.txt"), IsNil)
+ tmp.writeStagedTarget("C/clementine.txt", files["C/clementine.txt"])
+ c.Assert(r.AddTarget("C/clementine.txt", nil), IsNil)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 3)
+ // Only role2 should have a bumped version.
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
+ c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(4))
+ c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(4))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "3.targets.json": targetsKeyIDs,
+ "4.role1.json": role1ABKey.PublicData().IDs(),
+ "4.role2.json": concat(
+ role2CDKey1.PublicData().IDs(),
+ role2CDKey2.PublicData().IDs(),
+ role2CDKey3.PublicData().IDs(),
+ // Note that role1To2Key no longer signs since the role1 -> role2
+ // delegation was removed.
+ ),
+ })
+
+ // Targets should still be signed by the same roles.
+ checkTargets("targets", "potato.txt")
+ checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt")
+ checkTargets("role2", "C/clementine.txt", "D/durian.txt")
+
+ // Add back the role1 -> role2 delegation, and verify that it doesn't change
+ // existing targets in role2.json.
+ err = r.AddDelegatedRole("role1", role1To2, []*data.PublicKey{
+ role1To2Key.PublicData(),
+ })
+ c.Assert(err, IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 3)
+ // Both role1 and role2 should have a bumped version.
+ // role1 is bumped because the delegations changed.
+ // role2 is only bumped because its expiration is bumped.
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
+ c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(5))
+ c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(5))
+
+ checkTargets("targets", "potato.txt")
+ checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt")
+ checkTargets("role2", "C/clementine.txt", "D/durian.txt")
+}
+
+func (rs *RepoSuite) TestHashBinDelegations(c *C) {
+ tmp := newTmpDir(c)
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // Add one key to each role
+ genKey(c, r, "root")
+ targetsKeyIDs := genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ hb, err := targets.NewHashBins("bins_", 3)
+ if err != nil {
+ c.Assert(err, IsNil)
+ }
+
+ // Generate key for the intermediate bins role.
+ binsKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ err = local.SaveSigner("bins", binsKey)
+ c.Assert(err, IsNil)
+
+ // Generate key for the leaf bins role.
+ leafKey, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+ for i := uint64(0); i < hb.NumBins(); i++ {
+ b := hb.GetBin(i)
+ err = local.SaveSigner(b.RoleName(), leafKey)
+ if err != nil {
+ c.Assert(err, IsNil)
+ }
+ }
+
+ err = r.AddDelegatedRole("targets", data.DelegatedRole{
+ Name: "bins",
+ KeyIDs: binsKey.PublicData().IDs(),
+ Paths: []string{"*.txt"},
+ Threshold: 1,
+ }, []*data.PublicKey{
+ binsKey.PublicData(),
+ })
+ c.Assert(err, IsNil)
+
+ err = r.AddDelegatedRolesForPathHashBins("bins", hb, []*data.PublicKey{leafKey.PublicData()}, 1)
+ c.Assert(err, IsNil)
+ targets, err := r.targets("bins")
+ c.Assert(err, IsNil)
+ c.Assert(targets.Delegations.Roles, HasLen, 8)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ tmp.writeStagedTarget("foo.txt", "foo")
+ err = r.AddTarget("foo.txt", nil)
+ c.Assert(err, IsNil)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err := r.snapshot()
+ c.Assert(err, IsNil)
+ // 1 targets.json, 1 bins.json, 8 bins_*.json.
+ c.Assert(snapshot.Meta, HasLen, 10)
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["bins.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["bins_0-1.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["bins_2-3.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["bins_4-5.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["bins_6-7.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["bins_8-9.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["bins_a-b.json"].Version, Equals, int64(1))
+ c.Assert(snapshot.Meta["bins_c-d.json"].Version, Equals, int64(2))
+ c.Assert(snapshot.Meta["bins_e-f.json"].Version, Equals, int64(1))
+
+ targets, err = r.targets("bins_c-d")
+ c.Assert(err, IsNil)
+ c.Assert(targets.Targets, HasLen, 1)
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "targets.json": targetsKeyIDs,
+ "1.bins.json": binsKey.PublicData().IDs(),
+ "1.bins_0-1.json": leafKey.PublicData().IDs(),
+ "1.bins_2-3.json": leafKey.PublicData().IDs(),
+ "1.bins_4-5.json": leafKey.PublicData().IDs(),
+ "1.bins_6-7.json": leafKey.PublicData().IDs(),
+ "1.bins_8-9.json": leafKey.PublicData().IDs(),
+ "1.bins_a-b.json": leafKey.PublicData().IDs(),
+ "1.bins_c-d.json": leafKey.PublicData().IDs(),
+ "2.bins_c-d.json": leafKey.PublicData().IDs(),
+ "1.bins_e-f.json": leafKey.PublicData().IDs(),
+ })
+}
+
+func (rs *RepoSuite) TestResetTargetsDelegationsWithExpires(c *C) {
+ tmp := newTmpDir(c)
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // Add one key to each role
+ genKey(c, r, "root")
+ targetsKeyIDs := genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ // commit the metadata to the store.
+ c.Assert(r.AddTargets([]string{}, nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err := r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 1)
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "1.targets.json": targetsKeyIDs,
+ })
+
+ role1Key, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+
+ err = local.SaveSigner("role1", role1Key)
+ c.Assert(err, IsNil)
+
+ // Delegate from targets -> role1 for A/*, B/* with one key, threshold 1.
+ role1 := data.DelegatedRole{
+ Name: "role1",
+ KeyIDs: role1Key.PublicData().IDs(),
+ Paths: []string{"A/*", "B/*"},
+ Threshold: 1,
+ }
+ err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{
+ role1Key.PublicData(),
+ })
+ c.Assert(err, IsNil)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 2)
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(2))
+ c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "1.targets.json": targetsKeyIDs,
+ "targets.json": targetsKeyIDs,
+ "1.role1.json": role1Key.PublicData().IDs(),
+ "role1.json": role1Key.PublicData().IDs(),
+ })
+
+ c.Assert(r.ResetTargetsDelegations("targets"), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ snapshot, err = r.snapshot()
+ c.Assert(err, IsNil)
+ c.Assert(snapshot.Meta, HasLen, 2)
+ c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
+ c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1))
+
+ checkSigKeyIDs(c, local, map[string][]string{
+ "2.targets.json": targetsKeyIDs,
+ "targets.json": targetsKeyIDs,
+ "1.role1.json": role1Key.PublicData().IDs(),
+ "role1.json": role1Key.PublicData().IDs(),
+ })
+}
+
+func (rs *RepoSuite) TestSignWithDelegations(c *C) {
+ tmp := newTmpDir(c)
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // Add one key to each role
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ role1Key, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+
+ role1 := data.DelegatedRole{
+ Name: "role1",
+ KeyIDs: role1Key.PublicData().IDs(),
+ Paths: []string{"A/*", "B/*"},
+ Threshold: 1,
+ }
+ err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{
+ role1Key.PublicData(),
+ })
+ c.Assert(err, IsNil)
+
+ // targets.json should be signed, but role1.json is not signed because there
+ // is no key in the local store.
+ m, err := local.GetMeta()
+ c.Assert(err, IsNil)
+ targetsMeta := &data.Signed{}
+ c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil)
+ c.Assert(len(targetsMeta.Signatures), Equals, 1)
+ role1Meta := &data.Signed{}
+ c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil)
+ c.Assert(len(role1Meta.Signatures), Equals, 0)
+
+ c.Assert(r.Snapshot(), DeepEquals, ErrInsufficientSignatures{"role1.json", verify.ErrNoSignatures})
+
+ // Sign role1.json.
+ c.Assert(local.SaveSigner("role1", role1Key), IsNil)
+ c.Assert(r.Sign("role1.json"), IsNil)
+
+ m, err = local.GetMeta()
+ c.Assert(err, IsNil)
+ targetsMeta = &data.Signed{}
+ c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil)
+ c.Assert(len(targetsMeta.Signatures), Equals, 1)
+ role1Meta = &data.Signed{}
+ c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil)
+ c.Assert(len(role1Meta.Signatures), Equals, 1)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+}
+
+func (rs *RepoSuite) TestAddOrUpdateSignatureWithDelegations(c *C) {
+ tmp := newTmpDir(c)
+ local := FileSystemStore(tmp.path, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // Add one key to each role
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+
+ role1Key, err := keys.GenerateEd25519Key()
+ c.Assert(err, IsNil)
+
+ role1 := data.DelegatedRole{
+ Name: "role1",
+ KeyIDs: role1Key.PublicData().IDs(),
+ Paths: []string{"A/*", "B/*"},
+ Threshold: 1,
+ }
+ err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{
+ role1Key.PublicData(),
+ })
+ c.Assert(err, IsNil)
+
+ // targets.json should be signed, but role1.json is not signed because there
+ // is no key in the local store.
+ m, err := local.GetMeta()
+ c.Assert(err, IsNil)
+ targetsMeta := &data.Signed{}
+ c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil)
+ c.Assert(len(targetsMeta.Signatures), Equals, 1)
+ role1Meta := &data.Signed{}
+ c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil)
+ c.Assert(len(role1Meta.Signatures), Equals, 0)
+
+ c.Assert(r.Snapshot(), DeepEquals, ErrInsufficientSignatures{"role1.json", verify.ErrNoSignatures})
+
+ // Sign role1.json.
+ canonical, err := cjson.EncodeCanonical(role1Meta.Signed)
+ c.Assert(err, IsNil)
+ sig, err := role1Key.SignMessage(canonical)
+ c.Assert(err, IsNil)
+ err = r.AddOrUpdateSignature("role1.json", data.Signature{
+ KeyID: role1Key.PublicData().IDs()[0],
+ Signature: sig,
+ })
+ c.Assert(err, IsNil)
+
+ m, err = local.GetMeta()
+ c.Assert(err, IsNil)
+ targetsMeta = &data.Signed{}
+ c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil)
+ c.Assert(len(targetsMeta.Signatures), Equals, 1)
+ role1Meta = &data.Signed{}
+ c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil)
+ c.Assert(len(role1Meta.Signatures), Equals, 1)
+
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+}
+
+// Test the offline signature flow: Payload -> SignPayload -> AddSignature
+func (rs *RepoSuite) TestOfflineFlow(c *C) {
+ // Set up repo.
+ meta := make(map[string]json.RawMessage)
+ local := MemoryStore(meta, nil)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+ c.Assert(r.Init(false), IsNil)
+ // Use ECDSA because it has a newline which is a difference between JSON and cJSON.
+ _, err = r.GenKeyWithSchemeAndExpires("root", data.DefaultExpires("root"), data.KeySchemeECDSA_SHA2_P256)
+ c.Assert(err, IsNil)
+
+ // Get the payload to sign
+ _, err = r.Payload("badrole.json")
+ c.Assert(err, Equals, ErrMissingMetadata{"badrole.json"})
+ _, err = r.Payload("root")
+ c.Assert(err, Equals, ErrMissingMetadata{"root"})
+ payload, err := r.Payload("root.json")
+ c.Assert(err, IsNil)
+
+ root, err := r.SignedMeta("root.json")
+ c.Assert(err, IsNil)
+ rootCanonical, err := cjson.EncodeCanonical(root.Signed)
+ c.Assert(err, IsNil)
+ if !bytes.Equal(payload, rootCanonical) {
+ c.Fatalf("Payload(): not canonical.\n%s\n%s", string(payload), string(rootCanonical))
+ }
+
+ // Sign the payload
+ _, err = r.SignRaw("targets", payload)
+ c.Assert(err, Equals, ErrNoKeys{"targets"})
+ signatures, err := r.SignRaw("root", payload)
+ c.Assert(err, IsNil)
+ c.Assert(len(signatures), Equals, 1)
+
+ // Add the payload signatures back
+ for _, sig := range signatures {
+ // This method checks that the signature verifies!
+ err = r.AddOrUpdateSignature("root.json", sig)
+ c.Assert(err, IsNil)
+ }
+}
+
+// Regression test: Snapshotting an invalid root should fail.
+func (rs *RepoSuite) TestSnapshotWithInvalidRoot(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // Init should create targets.json, but not signed yet
+ r.Init(false)
+
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+
+ // Clear the root signature so that signature verification fails.
+ s, err := r.SignedMeta("root.json")
+ c.Assert(err, IsNil)
+ c.Assert(s.Signatures, HasLen, 1)
+ s.Signatures[0].Signature = data.HexBytes{}
+ b, err := r.jsonMarshal(s)
+ c.Assert(err, IsNil)
+ r.meta["root.json"] = b
+ local.SetMeta("root.json", b)
+
+ // Snapshotting should fail.
+ c.Assert(r.Snapshot(), Equals, ErrInsufficientSignatures{
+ "root.json", verify.ErrRoleThreshold{Expected: 1, Actual: 0}})
+
+ // Correctly sign root
+ c.Assert(r.Sign("root.json"), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+}
+
+// Regression test: Do not omit length in target metadata files.
+func (rs *RepoSuite) TestTargetMetadataLength(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ // Init should create targets.json, but not signed yet
+ r.Init(false)
+
+ genKey(c, r, "root")
+ genKey(c, r, "targets")
+ genKey(c, r, "snapshot")
+ genKey(c, r, "timestamp")
+ c.Assert(r.AddTarget("foo.txt", nil), IsNil)
+ c.Assert(r.Snapshot(), IsNil)
+ c.Assert(r.Timestamp(), IsNil)
+ c.Assert(r.Commit(), IsNil)
+
+ // Check length field of foo.txt exists.
+ meta, err := local.GetMeta()
+ c.Assert(err, IsNil)
+ targetsJSON, ok := meta["targets.json"]
+ if !ok {
+ c.Fatal("missing targets metadata")
+ }
+ s := &data.Signed{}
+ c.Assert(json.Unmarshal(targetsJSON, s), IsNil)
+ fmt.Fprint(os.Stderr, s.Signed)
+ var objMap map[string]json.RawMessage
+ c.Assert(json.Unmarshal(s.Signed, &objMap), IsNil)
+ targetsMap, ok := objMap["targets"]
+ if !ok {
+ c.Fatal("missing targets field in targets metadata")
+ }
+ c.Assert(json.Unmarshal(targetsMap, &objMap), IsNil)
+ targetsMap, ok = objMap["foo.txt"]
+ if !ok {
+ c.Fatal("missing foo.txt in targets")
+ }
+ c.Assert(json.Unmarshal(targetsMap, &objMap), IsNil)
+ lengthMsg, ok := objMap["length"]
+ if !ok {
+ c.Fatal("missing length field in foo.txt file meta")
+ }
+ var length int64
+ c.Assert(json.Unmarshal(lengthMsg, &length), IsNil)
+ c.Assert(length, Equals, int64(0))
+}
+
+func (rs *RepoSuite) TestDeprecatedHexEncodedKeysFails(c *C) {
+ files := map[string][]byte{"foo.txt": []byte("foo")}
+ local := MemoryStore(make(map[string]json.RawMessage), files)
+ r, err := NewRepo(local)
+ c.Assert(err, IsNil)
+
+ r.Init(false)
+ // Add a root key with hex-encoded ecdsa format
+ signer, err := keys.GenerateEcdsaKey()
+ c.Assert(err, IsNil)
+ type deprecatedP256Verifier struct {
+ PublicKey data.HexBytes `json:"public"`
+ }
+ pub := signer.PublicKey
+ keyValBytes, err := json.Marshal(&deprecatedP256Verifier{PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)})
+ c.Assert(err, IsNil)
+ publicData := &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+ err = r.AddVerificationKey("root", publicData)
+ c.Assert(err, IsNil)
+ // TODO: AddVerificationKey does no validation, so perform a sign operation.
+ c.Assert(r.Sign("root.json"), ErrorMatches, "tuf: error unmarshalling key: invalid PEM value")
+}
diff --git a/requirements-test.txt b/requirements-test.txt
new file mode 100644
index 0000000..23822ee
--- /dev/null
+++ b/requirements-test.txt
@@ -0,0 +1,5 @@
+iso8601==2.0.0
+requests==2.31.0
+securesystemslib==0.28.0
+six==1.16.0
+tuf==3.0.0
diff --git a/sign/sign.go b/sign/sign.go
new file mode 100644
index 0000000..e31b546
--- /dev/null
+++ b/sign/sign.go
@@ -0,0 +1,80 @@
+package sign
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+const maxSignatures = 1024
+
+// MakeSignatures creates data.Signatures for canonical using signer k.
+//
+// There will be one data.Signature for each of k's IDs, each wih the same
+// signature data.
+func MakeSignatures(canonical []byte, k keys.Signer) ([]data.Signature, error) {
+ sigData, err := k.SignMessage(canonical)
+ if err != nil {
+ return nil, err
+ }
+
+ ids := k.PublicData().IDs()
+ signatures := make([]data.Signature, 0, len(ids))
+ for _, id := range ids {
+ signatures = append(signatures, data.Signature{
+ KeyID: id,
+ Signature: sigData,
+ })
+ }
+
+ return signatures, nil
+}
+
+// Sign signs the to-be-signed part of s using the signer k.
+//
+// The new signature(s) (one for each of k's key IDs) are appended to
+// s.Signatures. Existing signatures for the Key IDs are replaced.
+func Sign(s *data.Signed, k keys.Signer) error {
+ canonical, err := cjson.EncodeCanonical(s.Signed)
+ if err != nil {
+ return err
+ }
+
+ size := len(s.Signatures)
+ if size > maxSignatures-1 {
+ return errors.New("value too large")
+ }
+ signatures := make([]data.Signature, 0, size+1)
+ for _, oldSig := range s.Signatures {
+ if !k.PublicData().ContainsID(oldSig.KeyID) {
+ signatures = append(signatures, oldSig)
+ }
+ }
+
+ newSigs, err := MakeSignatures(canonical, k)
+ if err != nil {
+ return err
+ }
+ signatures = append(signatures, newSigs...)
+
+ s.Signatures = signatures
+ return nil
+}
+
+func Marshal(v interface{}, keys ...keys.Signer) (*data.Signed, error) {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return nil, err
+ }
+ s := &data.Signed{Signed: b}
+ for _, k := range keys {
+ if err := Sign(s, k); err != nil {
+ return nil, err
+ }
+
+ }
+ return s, nil
+}
diff --git a/util/util.go b/util/util.go
new file mode 100644
index 0000000..049169d
--- /dev/null
+++ b/util/util.go
@@ -0,0 +1,332 @@
+package util
+
+import (
+ "bytes"
+ "crypto/hmac"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "hash"
+ "io"
+ "os"
+ "path"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/theupdateframework/go-tuf/data"
+)
+
+type ErrWrongLength struct {
+ Expected int64
+ Actual int64
+}
+
+func (e ErrWrongLength) Error() string {
+ return fmt.Sprintf("wrong length, expected %d got %d", e.Expected, e.Actual)
+}
+
+type ErrWrongVersion struct {
+ Expected int64
+ Actual int64
+}
+
+func (e ErrWrongVersion) Error() string {
+ return fmt.Sprintf("wrong version, expected %d got %d", e.Expected, e.Actual)
+}
+
+type ErrWrongHash struct {
+ Type string
+ Expected data.HexBytes
+ Actual data.HexBytes
+}
+
+func (e ErrWrongHash) Error() string {
+ return fmt.Sprintf("wrong %s hash, expected %s got %s", e.Type, hex.EncodeToString(e.Expected), hex.EncodeToString(e.Actual))
+}
+
+type ErrNoCommonHash struct {
+ Expected data.Hashes
+ Actual data.Hashes
+}
+
+func (e ErrNoCommonHash) Error() string {
+ types := func(a data.Hashes) []string {
+ t := make([]string, 0, len(a))
+ for typ := range a {
+ t = append(t, typ)
+ }
+ return t
+ }
+ return fmt.Sprintf("no common hash function, expected one of %s, got %s", types(e.Expected), types(e.Actual))
+}
+
+type ErrUnknownHashAlgorithm struct {
+ Name string
+}
+
+func (e ErrUnknownHashAlgorithm) Error() string {
+ return fmt.Sprintf("unknown hash algorithm: %s", e.Name)
+}
+
+type PassphraseFunc func(role string, confirm bool, change bool) ([]byte, error)
+
+func FileMetaEqual(actual data.FileMeta, expected data.FileMeta) error {
+ if actual.Length != expected.Length {
+ return ErrWrongLength{expected.Length, actual.Length}
+ }
+
+ if err := hashEqual(actual.Hashes, expected.Hashes); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func BytesMatchLenAndHashes(fetched []byte, length int64, hashes data.Hashes) error {
+ flen := int64(len(fetched))
+ if length != 0 && flen != length {
+ return ErrWrongLength{length, flen}
+ }
+
+ for alg, expected := range hashes {
+ var h hash.Hash
+ switch alg {
+ case "sha256":
+ h = sha256.New()
+ case "sha512":
+ h = sha512.New()
+ default:
+ return ErrUnknownHashAlgorithm{alg}
+ }
+ h.Write(fetched)
+ hash := h.Sum(nil)
+ if !hmac.Equal(hash, expected) {
+ return ErrWrongHash{alg, expected, hash}
+ }
+ }
+
+ return nil
+}
+
+func hashEqual(actual data.Hashes, expected data.Hashes) error {
+ hashChecked := false
+ for typ, hash := range expected {
+ if h, ok := actual[typ]; ok {
+ hashChecked = true
+ if !hmac.Equal(h, hash) {
+ return ErrWrongHash{typ, hash, h}
+ }
+ }
+ }
+ if !hashChecked {
+ return ErrNoCommonHash{expected, actual}
+ }
+ return nil
+}
+
+func VersionEqual(actual int64, expected int64) error {
+ if actual != expected {
+ return ErrWrongVersion{expected, actual}
+ }
+ return nil
+}
+
+func SnapshotFileMetaEqual(actual data.SnapshotFileMeta, expected data.SnapshotFileMeta) error {
+ // TUF-1.0 no longer considers the length and hashes to be a required
+ // member of snapshots. However they are considering requiring hashes
+ // for delegated roles to avoid an attack described in Section 5.6 of
+ // the Mercury paper:
+ // https://github.com/theupdateframework/specification/pull/40
+ if expected.Length != 0 && actual.Length != expected.Length {
+ return ErrWrongLength{expected.Length, actual.Length}
+ }
+ // 5.6.2 - Check against snapshot role's targets hash
+ if len(expected.Hashes) != 0 {
+ if err := hashEqual(actual.Hashes, expected.Hashes); err != nil {
+ return err
+ }
+ }
+ // 5.6.4 - Check against snapshot role's snapshot version
+ if err := VersionEqual(actual.Version, expected.Version); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func TargetFileMetaEqual(actual data.TargetFileMeta, expected data.TargetFileMeta) error {
+ return FileMetaEqual(actual.FileMeta, expected.FileMeta)
+}
+
+func TimestampFileMetaEqual(actual data.TimestampFileMeta, expected data.TimestampFileMeta) error {
+ // TUF no longer considers the length and hashes to be a required
+ // member of Timestamp.
+ if expected.Length != 0 && actual.Length != expected.Length {
+ return ErrWrongLength{expected.Length, actual.Length}
+ }
+ // 5.5.2 - Check against timestamp role's snapshot hash
+ if len(expected.Hashes) != 0 {
+ if err := hashEqual(actual.Hashes, expected.Hashes); err != nil {
+ return err
+ }
+ }
+ // 5.5.4 - Check against timestamp role's snapshot version
+ if err := VersionEqual(actual.Version, expected.Version); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+const defaultHashAlgorithm = "sha512"
+
+func GenerateFileMeta(r io.Reader, hashAlgorithms ...string) (data.FileMeta, error) {
+ if len(hashAlgorithms) == 0 {
+ hashAlgorithms = []string{defaultHashAlgorithm}
+ }
+ hashes := make(map[string]hash.Hash, len(hashAlgorithms))
+ for _, hashAlgorithm := range hashAlgorithms {
+ var h hash.Hash
+ switch hashAlgorithm {
+ case "sha256":
+ h = sha256.New()
+ case "sha512":
+ h = sha512.New()
+ default:
+ return data.FileMeta{}, ErrUnknownHashAlgorithm{hashAlgorithm}
+ }
+ hashes[hashAlgorithm] = h
+ r = io.TeeReader(r, h)
+ }
+ n, err := io.Copy(io.Discard, r)
+ if err != nil {
+ return data.FileMeta{}, err
+ }
+ m := data.FileMeta{Length: n, Hashes: make(data.Hashes, len(hashes))}
+ for hashAlgorithm, h := range hashes {
+ m.Hashes[hashAlgorithm] = h.Sum(nil)
+ }
+ return m, nil
+}
+
+type versionedMeta struct {
+ Version int64 `json:"version"`
+}
+
+func generateVersionedFileMeta(r io.Reader, hashAlgorithms ...string) (data.FileMeta, int64, error) {
+ b, err := io.ReadAll(r)
+ if err != nil {
+ return data.FileMeta{}, 0, err
+ }
+
+ m, err := GenerateFileMeta(bytes.NewReader(b), hashAlgorithms...)
+ if err != nil {
+ return data.FileMeta{}, 0, err
+ }
+
+ s := data.Signed{}
+ if err := json.Unmarshal(b, &s); err != nil {
+ return data.FileMeta{}, 0, err
+ }
+
+ vm := versionedMeta{}
+ if err := json.Unmarshal(s.Signed, &vm); err != nil {
+ return data.FileMeta{}, 0, err
+ }
+
+ return m, vm.Version, nil
+}
+
+func GenerateSnapshotFileMeta(r io.Reader, hashAlgorithms ...string) (data.SnapshotFileMeta, error) {
+ m, v, err := generateVersionedFileMeta(r, hashAlgorithms...)
+ if err != nil {
+ return data.SnapshotFileMeta{}, err
+ }
+ return data.SnapshotFileMeta{
+ Length: m.Length,
+ Hashes: m.Hashes,
+ Version: v,
+ }, nil
+}
+
+func GenerateTargetFileMeta(r io.Reader, hashAlgorithms ...string) (data.TargetFileMeta, error) {
+ m, err := GenerateFileMeta(r, hashAlgorithms...)
+ if err != nil {
+ return data.TargetFileMeta{}, err
+ }
+ return data.TargetFileMeta{
+ FileMeta: m,
+ }, nil
+}
+
+func GenerateTimestampFileMeta(r io.Reader, hashAlgorithms ...string) (data.TimestampFileMeta, error) {
+ m, v, err := generateVersionedFileMeta(r, hashAlgorithms...)
+ if err != nil {
+ return data.TimestampFileMeta{}, err
+ }
+ return data.TimestampFileMeta{
+ Length: m.Length,
+ Hashes: m.Hashes,
+ Version: v,
+ }, nil
+}
+
+func NormalizeTarget(p string) string {
+ // FIXME(TUF-0.9) TUF-1.0 is considering banning paths that begin with
+ // a leading path separator, to avoid surprising behavior when joining
+ // target and delgated paths. python-tuf raises an exception if any
+ // path starts with '/', but since we need to be cross compatible with
+ // TUF-0.9 we still need to support leading slashes. For now, we will
+ // just strip them out, but eventually we should also consider turning
+ // them into an error.
+ return strings.TrimPrefix(path.Join("/", p), "/")
+}
+
+func VersionedPath(p string, version int64) string {
+ return path.Join(path.Dir(p), strconv.FormatInt(version, 10)+"."+path.Base(p))
+}
+
+func HashedPaths(p string, hashes data.Hashes) []string {
+ paths := make([]string, 0, len(hashes))
+ for _, hash := range hashes {
+ hashedPath := path.Join(path.Dir(p), hash.String()+"."+path.Base(p))
+ paths = append(paths, hashedPath)
+ }
+ return paths
+}
+
+func AtomicallyWriteFile(filename string, data []byte, perm os.FileMode) error {
+ dir, name := filepath.Split(filename)
+ f, err := os.CreateTemp(dir, name)
+ if err != nil {
+ return err
+ }
+
+ _, err = f.Write(data)
+ if err != nil {
+ f.Close()
+ os.Remove(f.Name())
+ return err
+ }
+
+ if err = f.Chmod(perm); err != nil {
+ f.Close()
+ os.Remove(f.Name())
+ return err
+ }
+
+ if err := f.Close(); err != nil {
+ os.Remove(f.Name())
+ return err
+ }
+
+ if err := os.Rename(f.Name(), filename); err != nil {
+ os.Remove(f.Name())
+ return err
+ }
+
+ return nil
+}
diff --git a/util/util_test.go b/util/util_test.go
new file mode 100644
index 0000000..ca691ef
--- /dev/null
+++ b/util/util_test.go
@@ -0,0 +1,282 @@
+package util
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/hex"
+ "hash"
+ "testing"
+
+ "github.com/theupdateframework/go-tuf/data"
+ . "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type UtilSuite struct{}
+
+var _ = Suite(&UtilSuite{})
+
+func (UtilSuite) TestGenerateTargetFileMetaDefault(c *C) {
+ // default is sha512
+ r := bytes.NewReader([]byte("foo"))
+ meta, err := GenerateTargetFileMeta(r)
+ c.Assert(err, IsNil)
+ c.Assert(meta.Length, Equals, int64(3))
+ hashes := meta.Hashes
+ c.Assert(hashes, HasLen, 1)
+ hash, ok := hashes["sha512"]
+ if !ok {
+ c.Fatal("missing sha512 hash")
+ }
+ c.Assert(hash.String(), DeepEquals, "f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7")
+}
+
+func (UtilSuite) TestGenerateTargetFileMetaExplicit(c *C) {
+ r := bytes.NewReader([]byte("foo"))
+ meta, err := GenerateTargetFileMeta(r, "sha256", "sha512")
+ c.Assert(err, IsNil)
+ c.Assert(meta.Length, Equals, int64(3))
+ hashes := meta.Hashes
+ c.Assert(hashes, HasLen, 2)
+ for name, val := range map[string]string{
+ "sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
+ "sha512": "f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7",
+ } {
+ hash, ok := hashes[name]
+ if !ok {
+ c.Fatalf("missing %s hash", name)
+ }
+ c.Assert(hash.String(), DeepEquals, val)
+ }
+}
+
+func makeHashes(c *C, hashes map[string]string) data.Hashes {
+ h := make(map[string]data.HexBytes, len(hashes))
+ for typ, hash := range hashes {
+ v, err := hex.DecodeString(hash)
+ c.Assert(err, IsNil)
+ h[typ] = v
+ }
+ return h
+}
+
+type testMetaFile struct {
+ name string
+ actual data.FileMeta
+ expected data.FileMeta
+ err func(testMetaFile) error
+}
+
+func testMetaFileCases(c *C) []testMetaFile {
+ fileMeta := func(c *C, length int64, hashes map[string]string) data.FileMeta {
+ return data.FileMeta{
+ Length: length,
+ Hashes: makeHashes(c, hashes),
+ }
+ }
+
+ return []testMetaFile{
+ {
+ name: "wrong length",
+ actual: data.FileMeta{Length: 1},
+ expected: data.FileMeta{Length: 2},
+ err: func(testMetaFile) error { return ErrWrongLength{Actual: 1, Expected: 2} },
+ },
+ {
+ name: "wrong sha512 hash",
+ actual: fileMeta(c, 10, map[string]string{"sha512": "111111"}),
+ expected: fileMeta(c, 10, map[string]string{"sha512": "222222"}),
+ err: func(t testMetaFile) error {
+ return ErrWrongHash{"sha512", t.expected.Hashes["sha512"], t.actual.Hashes["sha512"]}
+ },
+ },
+ {
+ name: "intersecting hashes",
+ actual: fileMeta(c, 10, map[string]string{"sha512": "111111", "md5": "222222"}),
+ expected: fileMeta(c, 10, map[string]string{"sha512": "111111", "sha256": "333333"}),
+ err: func(testMetaFile) error { return nil },
+ },
+ {
+ name: "no common hashes",
+ actual: fileMeta(c, 10, map[string]string{"sha512": "111111"}),
+ expected: fileMeta(c, 10, map[string]string{"sha256": "222222", "md5": "333333"}),
+ err: func(t testMetaFile) error { return ErrNoCommonHash{t.expected.Hashes, t.actual.Hashes} },
+ },
+ }
+}
+
+func (UtilSuite) TestSnapshotFileMetaEqual(c *C) {
+ type test struct {
+ name string
+ actual data.SnapshotFileMeta
+ expected data.SnapshotFileMeta
+ err func(test) error
+ }
+
+ fileMeta := func(version int64, length int64, hashes map[string]string) data.SnapshotFileMeta {
+ return data.SnapshotFileMeta{
+ Length: length,
+ Hashes: makeHashes(c, hashes),
+ Version: version,
+ }
+ }
+
+ tests := []test{
+ {
+ name: "same version",
+ actual: fileMeta(1, 10, map[string]string{"sha512": "111111"}),
+ expected: fileMeta(1, 10, map[string]string{"sha512": "111111"}),
+ err: func(test) error { return nil },
+ },
+ {
+ name: "wrong version",
+ actual: fileMeta(0, 10, map[string]string{"sha512": "111111"}),
+ expected: fileMeta(1, 10, map[string]string{"sha512": "111111"}),
+ err: func(test) error { return ErrWrongVersion{Expected: 1, Actual: 0} },
+ },
+ {
+ name: "wrong version",
+ actual: fileMeta(1, 10, map[string]string{"sha512": "111111"}),
+ expected: fileMeta(0, 10, map[string]string{"sha512": "111111"}),
+ err: func(test) error { return ErrWrongVersion{Expected: 0, Actual: 1} },
+ },
+ {
+ name: "wrong version",
+ actual: fileMeta(1, 10, map[string]string{"sha512": "111111"}),
+ expected: fileMeta(2, 10, map[string]string{"sha512": "111111"}),
+ err: func(test) error { return ErrWrongVersion{Expected: 2, Actual: 1} },
+ },
+ }
+
+ for _, t := range tests {
+ c.Assert(SnapshotFileMetaEqual(t.actual, t.expected), DeepEquals, t.err(t), Commentf("name = %s", t.name))
+ }
+
+ for _, t := range testMetaFileCases(c) {
+ actual := data.SnapshotFileMeta{Length: t.actual.Length, Hashes: t.actual.Hashes}
+ expected := data.SnapshotFileMeta{Length: t.expected.Length, Hashes: t.expected.Hashes}
+ c.Assert(SnapshotFileMetaEqual(actual, expected), DeepEquals, t.err(t), Commentf("name = %s %d %d", t.name, t.actual.Length, t.expected.Length))
+ c.Assert(FileMetaEqual(t.actual, t.expected), DeepEquals, t.err(t), Commentf("name = %s", t.name))
+ }
+}
+
+func (UtilSuite) TestNormalizeTarget(c *C) {
+ for before, after := range map[string]string{
+ "": "",
+ "foo.txt": "foo.txt",
+ "/bar.txt": "bar.txt",
+ "foo//bar.txt": "foo/bar.txt",
+ "/with/./a/dot": "with/a/dot",
+ "/with/double/../dot": "with/dot",
+ } {
+ c.Assert(NormalizeTarget(before), Equals, after)
+ }
+}
+
+func (UtilSuite) TestHashedPaths(c *C) {
+ hexBytes := func(s string) data.HexBytes {
+ v, err := hex.DecodeString(s)
+ c.Assert(err, IsNil)
+ return v
+ }
+ hashes := data.Hashes{
+ "sha512": hexBytes("abc123"),
+ "sha256": hexBytes("def456"),
+ }
+ paths := HashedPaths("foo/bar.txt", hashes)
+ // cannot use DeepEquals as the returned order is non-deterministic
+ c.Assert(paths, HasLen, 2)
+ expected := map[string]struct{}{"foo/abc123.bar.txt": {}, "foo/def456.bar.txt": {}}
+ for _, path := range paths {
+ if _, ok := expected[path]; !ok {
+ c.Fatalf("unexpected path: %s", path)
+ }
+ delete(expected, path)
+ }
+}
+
+func (UtilSuite) TestVersionEqual(c *C) {
+ c.Assert(VersionEqual(1, 1), IsNil)
+ c.Assert(VersionEqual(1, 3), Equals, ErrWrongVersion{3, 1})
+}
+
+func makeHash(b []byte, alg string) []byte {
+ var h hash.Hash
+
+ switch alg {
+ case "sha256":
+ h = sha256.New()
+ case "sha512":
+ h = sha512.New()
+ }
+ h.Write(b)
+ return h.Sum(nil)
+}
+
+func (UtilSuite) TestBytesMatchLenAndHashes(c *C) {
+ type test struct {
+ name string
+ bytes []byte
+ length int64
+ hashes data.Hashes
+ err func(test) error
+ }
+
+ b := []byte{82, 253, 252, 7, 33, 130, 101, 79, 22, 63, 95, 15, 154, 98, 29, 114}
+ bhashes := data.Hashes{
+ "sha512": makeHash(b, "sha512"),
+ "sha256": makeHash(b, "sha256"),
+ }
+
+ tests := []test{
+ {
+ name: "correct len and hashes",
+ bytes: b,
+ length: 16,
+ hashes: bhashes,
+ err: func(test) error { return nil },
+ },
+ {
+ name: "incorrect len",
+ bytes: b,
+ length: 32,
+ hashes: bhashes,
+ err: func(test) error { return ErrWrongLength{32, 16} },
+ },
+ {
+ name: "incorrect hashes sha512",
+ bytes: b,
+ length: 16,
+ hashes: data.Hashes{
+ "sha512": makeHash(b, "sha256"),
+ },
+ err: func(test) error { return ErrWrongHash{"sha512", bhashes["sha256"], bhashes["sha512"]} },
+ },
+ {
+ name: "incorrect hashes sha256",
+ bytes: b,
+ length: 16,
+ hashes: data.Hashes{
+ "sha256": makeHash(b, "sha512"),
+ },
+ err: func(test) error { return ErrWrongHash{"sha256", bhashes["sha512"], bhashes["sha256"]} },
+ },
+ {
+ name: "incorrect len and hashes",
+ bytes: b,
+ length: 32,
+ hashes: data.Hashes{
+ "sha512": makeHash(b, "sha256"),
+ "sha256": makeHash(b, "sha512"),
+ },
+ err: func(test) error { return ErrWrongLength{32, 16} },
+ },
+ }
+
+ for _, t := range tests {
+ c.Assert(BytesMatchLenAndHashes(t.bytes, t.length, t.hashes), DeepEquals, t.err(t), Commentf("name = %s", t.name))
+ }
+}
diff --git a/verify/db.go b/verify/db.go
new file mode 100644
index 0000000..04f5bf1
--- /dev/null
+++ b/verify/db.go
@@ -0,0 +1,104 @@
+package verify
+
+import (
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/internal/roles"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+type Role struct {
+ KeyIDs map[string]struct{}
+ Threshold int
+}
+
+func (r *Role) ValidKey(id string) bool {
+ _, ok := r.KeyIDs[id]
+ return ok
+}
+
+type DB struct {
+ roles map[string]*Role
+ verifiers map[string]keys.Verifier
+}
+
+func NewDB() *DB {
+ return &DB{
+ roles: make(map[string]*Role),
+ verifiers: make(map[string]keys.Verifier),
+ }
+}
+
+// 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 nil, ErrInvalidDelegatedRole
+ }
+ role := &data.Role{Threshold: r.Threshold, KeyIDs: r.KeyIDs}
+ if err := db.AddRole(r.Name, role); err != nil {
+ return nil, err
+ }
+ }
+ for id, k := range d.Keys {
+ if err := db.AddKey(id, k); err != nil {
+ return nil, err
+ }
+ }
+ return db, nil
+}
+
+func (db *DB) AddKey(id string, k *data.PublicKey) error {
+ verifier, err := keys.GetVerifier(k)
+ if err != nil {
+ return err // ErrInvalidKey
+ }
+
+ // TUF is considering in TAP-12 removing the
+ // requirement that the keyid hash algorithm be derived
+ // from the public key. So to be forwards compatible,
+ // we allow any key ID, rather than checking k.ContainsID(id)
+ //
+ // AddKey should be idempotent, so we allow re-adding the same PublicKey.
+ //
+ // TAP-12: https://github.com/theupdateframework/taps/blob/master/tap12.md
+ if oldVerifier, exists := db.verifiers[id]; exists && oldVerifier.Public() != verifier.Public() {
+ return ErrRepeatID{id}
+ }
+
+ db.verifiers[id] = verifier
+ return nil
+}
+
+func (db *DB) AddRole(name string, r *data.Role) error {
+ if r.Threshold < 1 {
+ return ErrInvalidThreshold
+ }
+
+ role := &Role{
+ KeyIDs: make(map[string]struct{}),
+ Threshold: r.Threshold,
+ }
+ for _, id := range r.KeyIDs {
+ role.KeyIDs[id] = struct{}{}
+ }
+
+ db.roles[name] = role
+ return nil
+}
+
+func (db *DB) GetVerifier(id string) (keys.Verifier, error) {
+ k, ok := db.verifiers[id]
+ if !ok {
+ return nil, ErrMissingKey
+ }
+ return k, nil
+}
+
+func (db *DB) GetRole(name string) *Role {
+ return db.roles[name]
+}
diff --git a/verify/db_test.go b/verify/db_test.go
new file mode 100644
index 0000000..01d7a30
--- /dev/null
+++ b/verify/db_test.go
@@ -0,0 +1,131 @@
+package verify
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+func TestDelegationsDB(t *testing.T) {
+ key, err := keys.GenerateEd25519Key()
+ assert.Nil(t, err, "generating key failed")
+ var dbTests = []struct {
+ testName string
+ delegations *data.Delegations
+ initErr error
+ unmarshalErr error
+ }{
+ {
+ testName: "empty state",
+ delegations: &data.Delegations{},
+ unmarshalErr: ErrNoSignatures,
+ },
+ {
+ testName: "top level role",
+ delegations: &data.Delegations{Roles: []data.DelegatedRole{
+ {Name: "root"},
+ }},
+ initErr: ErrInvalidDelegatedRole,
+ },
+ {
+ testName: "invalid role",
+ delegations: &data.Delegations{Roles: []data.DelegatedRole{
+ {Threshold: 0},
+ }},
+ initErr: ErrInvalidThreshold,
+ },
+ {
+ testName: "standard (SHA256) key IDs supported",
+ delegations: &data.Delegations{
+ Keys: map[string]*data.PublicKey{
+ key.PublicData().IDs()[0]: key.PublicData(),
+ },
+ Roles: []data.DelegatedRole{{
+ Name: "rolename",
+ KeyIDs: key.PublicData().IDs(),
+ Threshold: 1,
+ },
+ },
+ },
+ // If we get to ErrNoSignatures, we've passed key loading; see
+ // delegations_test.go to see tests that delegation DB *fully* works
+ // with valid signatures set up.
+ unmarshalErr: ErrNoSignatures,
+ },
+ {
+ testName: "arbitrary (non-SHA256, per TAP-12) key IDs supported",
+ delegations: &data.Delegations{
+ Keys: map[string]*data.PublicKey{
+ "a": key.PublicData(),
+ },
+ Roles: []data.DelegatedRole{{
+ Name: "rolename",
+ KeyIDs: []string{"a"},
+ Threshold: 1,
+ },
+ },
+ },
+ // If we get to ErrNoSignatures, we've passed key loading; see
+ // delegations_test.go to see tests that delegation DB *fully* works
+ // with valid signatures set up.
+ unmarshalErr: ErrNoSignatures,
+ },
+ }
+
+ for _, tt := range dbTests {
+ t.Run(tt.testName, func(t *testing.T) {
+ db, err := NewDBFromDelegations(tt.delegations)
+ assert.Equal(t, tt.initErr, err)
+ if err == nil {
+ assert.NotNil(t, db)
+ var targets data.Targets
+ err = db.Unmarshal([]byte(`{"a":"b"}`), targets, "tree", 0)
+ assert.Equal(t, tt.unmarshalErr, err)
+ }
+ })
+ }
+}
+
+// Test key database for compliance with TAP-12.
+//
+// Previously, every key's key ID was the SHA256 of the public key. TAP-12
+// allows arbitrary key IDs, with no loss in security.
+//
+// TAP-12: https://github.com/theupdateframework/taps/blob/master/tap12.md
+func TestTAP12(t *testing.T) {
+ db := NewDB()
+ // Need to use a signer type that supports random signatures.
+ key1, _ := keys.GenerateRsaKey()
+ key2, _ := keys.GenerateRsaKey()
+ msg := []byte("{}")
+ sig1, _ := key1.SignMessage(msg)
+ sig1Duplicate, _ := key1.SignMessage(msg)
+ assert.NotEqual(t, sig1, sig1Duplicate, "Signatures should be random")
+ sig2, _ := key2.SignMessage(msg)
+
+ // Idempotent: adding the same key with the same ID is okay.
+ assert.Nil(t, db.AddKey("key1", key1.PublicData()), "initial add")
+ assert.Nil(t, db.AddKey("key1", key1.PublicData()), "re-add")
+ // Adding a different key is allowed, unless the key ID is the same.
+ assert.Nil(t, db.AddKey("key2", key2.PublicData()), "different key with different ID")
+ assert.ErrorIs(t, db.AddKey("key1", key2.PublicData()), ErrRepeatID{"key1"}, "different key with same key ID")
+ assert.Nil(t, db.AddKey("key1-duplicate", key1.PublicData()), "same key with different ID should succeed")
+ assert.Nil(t, db.AddRole("diffkeys", &data.Role{
+ KeyIDs: []string{"key1", "key2"},
+ Threshold: 2,
+ }), "adding role")
+ assert.Nil(t, db.AddRole("samekeys", &data.Role{
+ KeyIDs: []string{"key1", "key1-alt"},
+ Threshold: 2,
+ }), "adding role")
+ assert.Nil(t, db.VerifySignatures(&data.Signed{
+ Signed: msg,
+ Signatures: []data.Signature{{KeyID: "key1", Signature: sig1}, {KeyID: "key2", Signature: sig2}},
+ }, "diffkeys"), "Signature with different keys: ")
+ assert.ErrorIs(t, db.VerifySignatures(&data.Signed{
+ Signed: msg,
+ Signatures: []data.Signature{{KeyID: "key1", Signature: sig1}, {KeyID: "key1-alt", Signature: sig1Duplicate}},
+ }, "samekeys"), ErrRoleThreshold{2, 1}, "Threshold signing with repeat key")
+}
diff --git a/verify/errors.go b/verify/errors.go
new file mode 100644
index 0000000..f71d4bd
--- /dev/null
+++ b/verify/errors.go
@@ -0,0 +1,73 @@
+package verify
+
+import (
+ "errors"
+ "fmt"
+ "time"
+)
+
+var (
+ ErrMissingKey = errors.New("tuf: missing key")
+ ErrNoSignatures = errors.New("tuf: data has no signatures")
+ ErrInvalid = errors.New("tuf: signature verification failed")
+ ErrWrongMethod = errors.New("tuf: invalid signature type")
+ ErrWrongMetaType = errors.New("tuf: meta file has wrong type")
+ ErrExists = errors.New("tuf: key already in db")
+ ErrInvalidKey = errors.New("tuf: invalid key")
+ ErrInvalidRole = errors.New("tuf: invalid role")
+ ErrInvalidDelegatedRole = errors.New("tuf: invalid delegated role")
+ ErrInvalidKeyID = errors.New("tuf: invalid key id")
+ ErrInvalidThreshold = errors.New("tuf: invalid role threshold")
+ ErrMissingTargetFile = errors.New("tuf: missing previously listed targets metadata file")
+)
+
+type ErrRepeatID struct {
+ KeyID string
+}
+
+func (e ErrRepeatID) Error() string {
+ return fmt.Sprintf("tuf: duplicate key id (%s)", e.KeyID)
+}
+
+type ErrUnknownRole struct {
+ Role string
+}
+
+func (e ErrUnknownRole) Error() string {
+ return fmt.Sprintf("tuf: unknown role %q", e.Role)
+}
+
+type ErrExpired struct {
+ Expired time.Time
+}
+
+func (e ErrExpired) Error() string {
+ return fmt.Sprintf("expired at %s", e.Expired)
+}
+
+type ErrLowVersion struct {
+ Actual int64
+ Current int64
+}
+
+func (e ErrLowVersion) Error() string {
+ return fmt.Sprintf("version %d is lower than current version %d", e.Actual, e.Current)
+}
+
+type ErrWrongVersion struct {
+ Given int64
+ Expected int64
+}
+
+func (e ErrWrongVersion) Error() string {
+ return fmt.Sprintf("version %d does not match the expected version %d", e.Given, e.Expected)
+}
+
+type ErrRoleThreshold struct {
+ Expected int
+ Actual int
+}
+
+func (e ErrRoleThreshold) Error() string {
+ return "tuf: valid signatures did not meet threshold"
+}
diff --git a/verify/verify.go b/verify/verify.go
new file mode 100644
index 0000000..e62042e
--- /dev/null
+++ b/verify/verify.go
@@ -0,0 +1,187 @@
+package verify
+
+import (
+ "encoding/json"
+ "strings"
+ "time"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/internal/roles"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+)
+
+type signedMeta struct {
+ Type string `json:"_type"`
+ Expires time.Time `json:"expires"`
+ Version int64 `json:"version"`
+}
+
+// VerifySignature takes a signed JSON message, a signature, and a
+// verifier and verifies the given signature on the JSON message
+// using the verifier. It returns an error if verification fails.
+func VerifySignature(signed json.RawMessage, sig data.HexBytes,
+ verifier keys.Verifier) error {
+ var decoded map[string]interface{}
+ if err := json.Unmarshal(signed, &decoded); err != nil {
+ return err
+ }
+ msg, err := cjson.EncodeCanonical(decoded)
+ if err != nil {
+ return err
+ }
+ return verifier.Verify(msg, sig)
+}
+
+func (db *DB) VerifyIgnoreExpiredCheck(s *data.Signed, role string, minVersion int64) error {
+ if err := db.VerifySignatures(s, role); err != nil {
+ return err
+ }
+
+ sm := &signedMeta{}
+ if err := json.Unmarshal(s.Signed, sm); err != nil {
+ return err
+ }
+
+ if roles.IsTopLevelRole(role) {
+ // Top-level roles can only sign metadata of the same type (e.g. snapshot
+ // metadata must be signed by the snapshot role).
+ if !strings.EqualFold(sm.Type, role) {
+ return ErrWrongMetaType
+ }
+ } else {
+ // Delegated (non-top-level) roles may only sign targets metadata.
+ if strings.ToLower(sm.Type) != "targets" {
+ return ErrWrongMetaType
+ }
+ }
+
+ if sm.Version < minVersion {
+ return ErrLowVersion{sm.Version, minVersion}
+ }
+
+ return nil
+}
+
+func (db *DB) Verify(s *data.Signed, role string, minVersion int64) error {
+ // Verify signatures and versions
+ err := db.VerifyIgnoreExpiredCheck(s, role, minVersion)
+
+ if err != nil {
+ return err
+ }
+
+ sm := &signedMeta{}
+ if err := json.Unmarshal(s.Signed, sm); err != nil {
+ return err
+ }
+ // Verify expiration
+ if IsExpired(sm.Expires) {
+ return ErrExpired{sm.Expires}
+ }
+
+ return nil
+}
+
+var IsExpired = func(t time.Time) bool {
+ return time.Until(t) <= 0
+}
+
+func (db *DB) VerifySignatures(s *data.Signed, role string) error {
+ if len(s.Signatures) == 0 {
+ return ErrNoSignatures
+ }
+
+ roleData := db.GetRole(role)
+ if roleData == nil {
+ return ErrUnknownRole{role}
+ }
+
+ // Verify that a threshold of keys signed the data. Since keys can have
+ // multiple key ids, we need to protect against multiple attached
+ // signatures that just differ on the key id.
+ verifiedKeyIDs := make(map[string]struct{})
+ numVerifiedKeys := 0
+ for _, sig := range s.Signatures {
+ if !roleData.ValidKey(sig.KeyID) {
+ continue
+ }
+ verifier, err := db.GetVerifier(sig.KeyID)
+ if err != nil {
+ continue
+ }
+
+ if err := VerifySignature(s.Signed, sig.Signature, verifier); err != nil {
+ // If a signature fails verification, don't count it towards the
+ // threshold but also return early and error out immediately.
+ // Note: Because of this, it is impossible to distinguish between
+ // an error of an invalid signature and a threshold not achieved.
+ // Invalid signatures lead to not achieving the threshold.
+ continue
+ }
+
+ // Only consider this key valid if we haven't seen any of it's
+ // key ids before.
+ // Careful: we must not rely on the key IDs _declared in the file_,
+ // instead we get to decide what key IDs this key correspond to.
+ // XXX dangerous; better stop supporting multiple key IDs altogether.
+ keyIDs := verifier.MarshalPublicKey().IDs()
+ wasKeySeen := false
+ for _, keyID := range keyIDs {
+ if _, present := verifiedKeyIDs[keyID]; present {
+ wasKeySeen = true
+ }
+ }
+ if !wasKeySeen {
+ for _, id := range keyIDs {
+ verifiedKeyIDs[id] = struct{}{}
+ }
+
+ numVerifiedKeys++
+ }
+ }
+ if numVerifiedKeys < roleData.Threshold {
+ return ErrRoleThreshold{roleData.Threshold, numVerifiedKeys}
+ }
+
+ return nil
+}
+
+func (db *DB) Unmarshal(b []byte, v interface{}, role string, minVersion int64) error {
+ s := &data.Signed{}
+ if err := json.Unmarshal(b, s); err != nil {
+ return err
+ }
+ if err := db.Verify(s, role, minVersion); err != nil {
+ return err
+ }
+ return json.Unmarshal(s.Signed, v)
+}
+
+// UnmarshalExpired is exactly like Unmarshal except ignores expired timestamp error.
+func (db *DB) UnmarshalIgnoreExpired(b []byte, v interface{}, role string, minVersion int64) error {
+ s := &data.Signed{}
+ if err := json.Unmarshal(b, s); err != nil {
+ return err
+ }
+ // Note: If verification fails, then we wont attempt to unmarshal
+ // unless when verification error is errExpired.
+ verifyErr := db.Verify(s, role, minVersion)
+ if verifyErr != nil {
+ if _, ok := verifyErr.(ErrExpired); !ok {
+ return verifyErr
+ }
+ }
+ return json.Unmarshal(s.Signed, v)
+}
+
+func (db *DB) UnmarshalTrusted(b []byte, v interface{}, role string) error {
+ s := &data.Signed{}
+ if err := json.Unmarshal(b, s); err != nil {
+ return err
+ }
+ if err := db.VerifySignatures(s, role); err != nil {
+ return err
+ }
+ return json.Unmarshal(s.Signed, v)
+}
diff --git a/verify/verify_test.go b/verify/verify_test.go
new file mode 100644
index 0000000..191c1ed
--- /dev/null
+++ b/verify/verify_test.go
@@ -0,0 +1,302 @@
+package verify
+
+import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/json"
+ "errors"
+ "testing"
+ "time"
+
+ "github.com/theupdateframework/go-tuf/data"
+ "github.com/theupdateframework/go-tuf/pkg/keys"
+ "github.com/theupdateframework/go-tuf/sign"
+ "golang.org/x/crypto/ed25519"
+
+ . "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type VerifySuite struct{}
+
+var _ = Suite(&VerifySuite{})
+
+type ecdsaSigner struct {
+ *ecdsa.PrivateKey
+}
+
+type ecdsaPublic struct {
+ PublicKey *keys.PKIXPublicKey `json:"public"`
+}
+
+func (s ecdsaSigner) PublicData() *data.PublicKey {
+ keyValBytes, _ := json.Marshal(ecdsaPublic{PublicKey: &keys.PKIXPublicKey{PublicKey: s.Public()}})
+ return &data.PublicKey{
+ Type: data.KeyTypeECDSA_SHA2_P256,
+ Scheme: data.KeySchemeECDSA_SHA2_P256,
+ Algorithms: data.HashAlgorithms,
+ Value: keyValBytes,
+ }
+}
+
+func (s ecdsaSigner) SignMessage(message []byte) ([]byte, error) {
+ hash := sha256.Sum256(message)
+ return s.PrivateKey.Sign(rand.Reader, hash[:], crypto.SHA256)
+}
+
+func (s ecdsaSigner) ContainsID(id string) bool {
+ return s.PublicData().ContainsID(id)
+}
+
+func (ecdsaSigner) MarshalPrivateKey() (*data.PrivateKey, error) {
+ return nil, errors.New("not implemented for test")
+}
+
+func (ecdsaSigner) UnmarshalPrivateKey(key *data.PrivateKey) error {
+ return errors.New("not implemented for test")
+}
+
+func (VerifySuite) Test(c *C) {
+ type test struct {
+ name string
+ keys []*data.PublicKey
+ roles map[string]*data.Role
+ s *data.Signed
+ ver int64
+ exp *time.Time
+ typ string
+ role string
+ err error
+ mut func(*test)
+ }
+
+ expiredTime := time.Now().Add(-time.Hour)
+ minVer := int64(10)
+ tests := []test{
+ {
+ name: "no signatures",
+ mut: func(t *test) { t.s.Signatures = []data.Signature{} },
+ err: ErrNoSignatures,
+ },
+ {
+ name: "unknown role",
+ role: "foo",
+ err: ErrUnknownRole{"foo"},
+ },
+ {
+ // It is impossible to distinguish between an error of an invalid
+ // signature and a threshold not achieved. Invalid signatures lead
+ // to not achieving the threshold.
+ name: "signature wrong length",
+ mut: func(t *test) { t.s.Signatures[0].Signature = []byte{0} },
+ err: ErrRoleThreshold{1, 0},
+ },
+ {
+ name: "key missing from role",
+ mut: func(t *test) { t.roles["root"].KeyIDs = nil },
+ err: ErrRoleThreshold{1, 0},
+ },
+ {
+ name: "invalid signature",
+ mut: func(t *test) { t.s.Signatures[0].Signature = make([]byte, ed25519.SignatureSize) },
+ err: ErrRoleThreshold{1, 0},
+ },
+ {
+ name: "enough signatures with extra invalid signature",
+ mut: func(t *test) {
+ t.s.Signatures = append(t.s.Signatures, data.Signature{
+ KeyID: t.s.Signatures[0].KeyID,
+ Signature: make([]byte, ed25519.SignatureSize)})
+ },
+ },
+ {
+ name: "not enough signatures",
+ mut: func(t *test) { t.roles["root"].Threshold = 2 },
+ err: ErrRoleThreshold{2, 1},
+ },
+ {
+ name: "exactly enough signatures",
+ },
+ {
+ name: "more than enough signatures",
+ mut: func(t *test) {
+ k, _ := keys.GenerateEd25519Key()
+ sign.Sign(t.s, k)
+ t.keys = append(t.keys, k.PublicData())
+ t.roles["root"].KeyIDs = append(t.roles["root"].KeyIDs, k.PublicData().IDs()...)
+ },
+ },
+ {
+ name: "duplicate key id",
+ mut: func(t *test) {
+ t.roles["root"].Threshold = 2
+ t.s.Signatures = append(t.s.Signatures, t.s.Signatures[0])
+ },
+ err: ErrRoleThreshold{2, 1},
+ },
+ {
+ name: "unknown key",
+ mut: func(t *test) {
+ k, _ := keys.GenerateEd25519Key()
+ sign.Sign(t.s, k)
+ },
+ },
+ {
+ name: "unknown key below threshold",
+ mut: func(t *test) {
+ k, _ := keys.GenerateEd25519Key()
+ sign.Sign(t.s, k)
+ t.roles["root"].Threshold = 2
+ },
+ err: ErrRoleThreshold{2, 1},
+ },
+ {
+ name: "unknown keys in db",
+ mut: func(t *test) {
+ k, _ := keys.GenerateEd25519Key()
+ sign.Sign(t.s, k)
+ t.keys = append(t.keys, k.PublicData())
+ },
+ },
+ {
+ name: "unknown keys in db below threshold",
+ mut: func(t *test) {
+ k, _ := keys.GenerateEd25519Key()
+ sign.Sign(t.s, k)
+ t.keys = append(t.keys, k.PublicData())
+ t.roles["root"].Threshold = 2
+ },
+ err: ErrRoleThreshold{2, 1},
+ },
+ {
+ name: "wrong type",
+ typ: "bar",
+ err: ErrWrongMetaType,
+ },
+ {
+ name: "low version",
+ ver: minVer - 1,
+ err: ErrLowVersion{minVer - 1, minVer},
+ },
+ {
+ name: "expired",
+ exp: &expiredTime,
+ err: ErrExpired{expiredTime},
+ },
+ {
+ name: "valid ecdsa signature",
+ mut: func(t *test) {
+ k, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ s := ecdsaSigner{k}
+ sign.Sign(t.s, s)
+ t.s.Signatures = t.s.Signatures[1:]
+ t.keys = []*data.PublicKey{s.PublicData()}
+ t.roles["root"].KeyIDs = s.PublicData().IDs()
+ },
+ },
+ {
+ // The threshold is still achieved.
+ name: "invalid second ecdsa signature",
+ mut: func(t *test) {
+ k, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ s := ecdsaSigner{k}
+ sign.Sign(t.s, s)
+ t.s.Signatures[1].Signature[0]++
+ t.keys = append(t.keys, s.PublicData())
+ t.roles["root"].KeyIDs = append(t.roles["root"].KeyIDs, s.PublicData().IDs()...)
+ },
+ },
+ }
+ for _, t := range tests {
+ if t.role == "" {
+ t.role = "root"
+ }
+ if t.ver == 0 {
+ t.ver = minVer
+ }
+ if t.exp == nil {
+ expires := time.Now().Add(time.Hour)
+ t.exp = &expires
+ }
+ if t.typ == "" {
+ t.typ = t.role
+ }
+ if t.keys == nil && t.s == nil {
+ k, _ := keys.GenerateEd25519Key()
+ t.s, _ = sign.Marshal(&signedMeta{Type: t.typ, Version: t.ver, Expires: *t.exp}, k)
+ t.keys = []*data.PublicKey{k.PublicData()}
+ }
+ if t.roles == nil {
+ t.roles = map[string]*data.Role{
+ "root": {
+ KeyIDs: t.keys[0].IDs(),
+ Threshold: 1,
+ },
+ }
+ }
+ if t.mut != nil {
+ t.mut(&t)
+ }
+
+ db := NewDB()
+ for _, k := range t.keys {
+ for _, id := range k.IDs() {
+ err := db.AddKey(id, k)
+ c.Assert(err, IsNil)
+ }
+ }
+ for n, r := range t.roles {
+ err := db.AddRole(n, r)
+ c.Assert(err, IsNil)
+ }
+
+ err := db.Verify(t.s, t.role, minVer)
+ if e, ok := t.err.(ErrExpired); ok {
+ assertErrExpired(c, err, e)
+ } else {
+ c.Assert(err, DeepEquals, t.err, Commentf("name = %s", t.name))
+ }
+ }
+}
+
+func (VerifySuite) TestVerifyIgnoreExpired(c *C) {
+ minVer := int64(10)
+ role := "root"
+ k, _ := keys.GenerateEd25519Key()
+ s, _ := sign.Marshal(&signedMeta{Type: role, Version: minVer, Expires: time.Now().Add(-time.Hour)}, k)
+ keys := []*data.PublicKey{k.PublicData()}
+ roles := map[string]*data.Role{
+ "root": {
+ KeyIDs: keys[0].IDs(),
+ Threshold: 1,
+ },
+ }
+
+ db := NewDB()
+ for _, k := range keys {
+ for _, id := range k.IDs() {
+ err := db.AddKey(id, k)
+ c.Assert(err, IsNil)
+ }
+ }
+ for n, r := range roles {
+ err := db.AddRole(n, r)
+ c.Assert(err, IsNil)
+ }
+
+ err := db.VerifyIgnoreExpiredCheck(s, role, minVer)
+ c.Assert(err, IsNil)
+}
+
+func assertErrExpired(c *C, err error, expected ErrExpired) {
+ actual, ok := err.(ErrExpired)
+ if !ok {
+ c.Fatalf("expected err to have type ErrExpired, got %T", err)
+ }
+ c.Assert(actual.Expired.Unix(), Equals, expected.Expired.Unix())
+}